@windyroad/itil 0.61.1 → 0.61.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.
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +7 -2
- package/agents/test/fixtures/proceed-new-genuinely-new.md +0 -43
- package/agents/test/fixtures/proceed-new-subtle-sibling.md +0 -47
- package/agents/test/fixtures/regression-p347-vs-p346.md +0 -54
- package/agents/test/hang-off-check.bats +0 -225
- package/hooks/test/block-list.bats +0 -153
- package/hooks/test/command-detect.bats +0 -181
- package/hooks/test/itil-assistant-output-gate.bats +0 -114
- package/hooks/test/itil-assistant-output-review.bats +0 -224
- package/hooks/test/itil-bash-polling-antipattern-detect.bats +0 -154
- package/hooks/test/itil-changeset-discipline.bats +0 -603
- package/hooks/test/itil-claude-space-protection.bats +0 -294
- package/hooks/test/itil-commit-trailer-transition-advisory.bats +0 -80
- package/hooks/test/itil-correction-detect.bats +0 -189
- package/hooks/test/itil-deferral-cadence-gate.bats +0 -187
- package/hooks/test/itil-fictional-defer-detect.bats +0 -292
- package/hooks/test/itil-fix-title-lifecycle-advisory.bats +0 -129
- package/hooks/test/itil-mid-loop-ask-detect.bats +0 -220
- package/hooks/test/itil-no-implement-draft-gate.bats +0 -210
- package/hooks/test/itil-pending-questions-surface.bats +0 -307
- package/hooks/test/itil-readme-refresh-discipline.bats +0 -642
- package/hooks/test/itil-rfc-oversight-nudge.bats +0 -83
- package/hooks/test/itil-rfc-trailer-advisory.bats +0 -375
- package/hooks/test/itil-story-mirror-migration-nudge.bats +0 -122
- package/hooks/test/manage-problem-enforce-create.bats +0 -424
- package/hooks/test/p057-staging-trap-detect.bats +0 -231
- package/hooks/test/pre-publish-intake-gate.bats +0 -144
- package/hooks/test/runtime-sid-marker.bats +0 -90
- package/hooks/test/session-id.bats +0 -370
- package/scripts/migrate-problems-add-type.sh +0 -116
- package/scripts/test/catchup-scan.bats +0 -274
- package/scripts/test/check-afk-accept-eligible.bats +0 -310
- package/scripts/test/check-fail-soft-skip-discipline.bats +0 -172
- package/scripts/test/check-fix-rfc-trace.bats +0 -164
- package/scripts/test/check-locale-discipline.bats +0 -291
- package/scripts/test/check-problems-readme-budget.bats +0 -192
- package/scripts/test/check-rfc-has-stories.bats +0 -69
- package/scripts/test/check-rfc-rejected-alternatives.bats +0 -108
- package/scripts/test/check-rfc-stories-ratified.bats +0 -82
- package/scripts/test/check-upstream-responses.bats +0 -503
- package/scripts/test/classify-readme-drift.bats +0 -392
- package/scripts/test/derive-first-dispatch.bats +0 -274
- package/scripts/test/derive-release-vehicle.bats +0 -454
- package/scripts/test/detect-unratified-stories-maps.bats +0 -60
- package/scripts/test/dual-tolerant-glob-rfc-002-t2.bats +0 -309
- package/scripts/test/effort-tally.bats +0 -170
- package/scripts/test/evaluate-relevance.bats +0 -773
- package/scripts/test/kind-a-commands.bats +0 -46
- package/scripts/test/mark-create-gate.bats +0 -59
- package/scripts/test/mark-story-oversight-confirmed.bats +0 -53
- package/scripts/test/migrate-problems-add-type.bats +0 -280
- package/scripts/test/migrate-story-status-mirror.bats +0 -167
- package/scripts/test/no-type-regression-guard.bats +0 -74
- package/scripts/test/oversight-mirror-corpus-lint.bats +0 -150
- package/scripts/test/plugin-exercise-index.bats +0 -386
- package/scripts/test/plugin-maturity-doc-lint.bats +0 -470
- package/scripts/test/plugin-maturity-populate.bats +0 -631
- package/scripts/test/plugin-maturity-render.bats +0 -469
- package/scripts/test/plugin-validate-ci-gate.bats +0 -208
- package/scripts/test/reconcile-readme.bats +0 -1007
- package/scripts/test/reconcile-rfcs.bats +0 -496
- package/scripts/test/reconcile-stories.bats +0 -173
- package/scripts/test/reconcile-story-maps.bats +0 -74
- package/scripts/test/release-watch-poll-loop.bats +0 -180
- package/scripts/test/resolve-governance-plugin-dirs.bats +0 -78
- package/scripts/test/rfc-stories-extension.bats +0 -175
- package/scripts/test/skill-invocations.bats +0 -535
- package/scripts/test/skill-md-dual-tolerant-coverage-rfc-002-t3.bats +0 -374
- package/scripts/test/story-oversight-lib.bats +0 -190
- package/scripts/test/update-jtbd-references-related-problems.bats +0 -178
- package/scripts/test/update-problem-references-section.bats +0 -242
- package/scripts/test/update-problem-rfcs-section.bats +0 -242
- package/scripts/test/update-references-section-sibling-helpers.bats +0 -80
- package/scripts/test/update-rfc-commits-section.bats +0 -77
- package/scripts/test/verify-iter-summary.bats +0 -196
- package/scripts/test/working-the-problem-traversal.bats +0 -116
- package/skills/capture-problem/test/capture-problem-step-1-5b-jtbd-trace.bats +0 -398
- package/skills/capture-problem/test/capture-problem.bats +0 -389
- package/skills/capture-story/test/capture-story-behavioural.bats +0 -317
- package/skills/capture-story-map/test/capture-story-map-behavioural.bats +0 -98
- package/skills/close-incident/test/close-incident-contract.bats +0 -146
- package/skills/link-incident/test/link-incident-contract.bats +0 -125
- package/skills/list-incidents/test/list-incidents-contract.bats +0 -115
- package/skills/list-problems/test/list-problems-contract.bats +0 -100
- package/skills/list-stories/test/list-stories-contract.bats +0 -127
- package/skills/list-story-maps/test/list-story-maps-contract.bats +0 -46
- package/skills/manage-incident/test/manage-incident-adr-044-contract.bats +0 -206
- package/skills/manage-incident/test/manage-incident-close-forwarder.bats +0 -67
- package/skills/manage-incident/test/manage-incident-link-forwarder.bats +0 -67
- package/skills/manage-incident/test/manage-incident-list-forwarder.bats +0 -68
- package/skills/manage-incident/test/manage-incident-mitigate-forwarder.bats +0 -66
- package/skills/manage-incident/test/manage-incident-restore-forwarder.bats +0 -66
- package/skills/manage-incident/test/manage-incident.bats +0 -171
- package/skills/manage-problem/test/manage-problem-adr-044-step4-derive-first.bats +0 -151
- package/skills/manage-problem/test/manage-problem-auto-migrate-step.bats +0 -53
- package/skills/manage-problem/test/manage-problem-concern-boundary.bats +0 -64
- package/skills/manage-problem/test/manage-problem-effort-buckets.bats +0 -56
- package/skills/manage-problem/test/manage-problem-external-root-cause-detection.bats +0 -119
- package/skills/manage-problem/test/manage-problem-first-run-intake-prompt.bats +0 -43
- package/skills/manage-problem/test/manage-problem-git-mv-restage-reminder.bats +0 -48
- package/skills/manage-problem/test/manage-problem-id-collision-guard.bats +0 -39
- package/skills/manage-problem/test/manage-problem-list-forwarder.bats +0 -66
- package/skills/manage-problem/test/manage-problem-next-id-origin-lookup.bats +0 -50
- package/skills/manage-problem/test/manage-problem-no-prose-options.bats +0 -74
- package/skills/manage-problem/test/manage-problem-output-formatting.bats +0 -30
- package/skills/manage-problem/test/manage-problem-parked-and-cache.bats +0 -73
- package/skills/manage-problem/test/manage-problem-readme-refresh-on-transition.bats +0 -62
- package/skills/manage-problem/test/manage-problem-readme-tie-break-order.bats +0 -318
- package/skills/manage-problem/test/manage-problem-readme-vq-sort-order.bats +0 -206
- package/skills/manage-problem/test/manage-problem-release-vehicle-seed.bats +0 -83
- package/skills/manage-problem/test/manage-problem-review-forwarder.bats +0 -71
- package/skills/manage-problem/test/manage-problem-step-9d-recovery-path.bats +0 -97
- package/skills/manage-problem/test/manage-problem-transition-forwarder.bats +0 -96
- package/skills/manage-problem/test/manage-problem-transitive-dependencies.bats +0 -365
- package/skills/manage-problem/test/manage-problem-verification-detection.bats +0 -64
- package/skills/manage-problem/test/manage-problem-verification-pending.bats +0 -114
- package/skills/manage-problem/test/manage-problem-work-forwarder.bats +0 -88
- package/skills/manage-story/test/manage-story-contract.bats +0 -174
- package/skills/manage-story-map/test/manage-story-map-contract.bats +0 -63
- package/skills/mitigate-incident/test/mitigate-incident-contract.bats +0 -258
- package/skills/reconcile-readme/test/reconcile-readme-contract.bats +0 -201
- package/skills/report-upstream/test/report-upstream-contract.bats +0 -394
- package/skills/restore-incident/test/restore-incident-contract.bats +0 -170
- package/skills/review-problems/test/inbound-channels-cache-shape.bats +0 -127
- package/skills/review-problems/test/inbound-discovery-contract.bats +0 -383
- package/skills/review-problems/test/jtbd-301-verdict-shape-contract.bats +0 -225
- package/skills/review-problems/test/review-problems-contract.bats +0 -235
- package/skills/review-problems/test/review-problems-likely-verified-cell-shape.bats +0 -229
- package/skills/scaffold-intake/test/scaffold-intake-contract.bats +0 -126
- package/skills/scaffold-intake/test/scaffold-intake-fixture.bats +0 -164
- package/skills/scaffold-intake/test/scaffold-intake-secrets-absent.bats +0 -87
- package/skills/transition-problem/test/transition-problem-contract.bats +0 -285
- package/skills/transition-problems/test/transition-problems-contract.bats +0 -331
- package/skills/update-upstream/test/update-upstream-contract.bats +0 -375
- package/skills/work-problem/test/work-problem-contract.bats +0 -294
- package/skills/work-problems/test/work-problems-above-appetite-remediation.bats +0 -139
- package/skills/work-problems/test/work-problems-adr-013-rule-6-p352-amendment.bats +0 -166
- package/skills/work-problems/test/work-problems-auto-migrate-step.bats +0 -57
- package/skills/work-problems/test/work-problems-cost-logging.bats +0 -109
- package/skills/work-problems/test/work-problems-deviation-candidate-shape.bats +0 -121
- package/skills/work-problems/test/work-problems-first-run-intake-prompt.bats +0 -36
- package/skills/work-problems/test/work-problems-inter-iteration-verify.bats +0 -59
- package/skills/work-problems/test/work-problems-mid-loop-userpromptsubmit-handler.bats +0 -69
- package/skills/work-problems/test/work-problems-no-mid-loop-asking.bats +0 -192
- package/skills/work-problems/test/work-problems-p341-pre-all-done-gate.bats +0 -122
- package/skills/work-problems/test/work-problems-p342-r007-derive-then-pass-flags.bats +0 -170
- package/skills/work-problems/test/work-problems-p342-retro-auto-ticket-carveout.bats +0 -120
- package/skills/work-problems/test/work-problems-preflight-failure-handling.bats +0 -248
- package/skills/work-problems/test/work-problems-preflight-session-continuity.bats +0 -139
- package/skills/work-problems/test/work-problems-preflight.bats +0 -69
- package/skills/work-problems/test/work-problems-release-cadence.bats +0 -63
- package/skills/work-problems/test/work-problems-step-0-iter-error-staleness.bats +0 -93
- package/skills/work-problems/test/work-problems-step-0b-cache-staleness-behavioural.bats +0 -138
- package/skills/work-problems/test/work-problems-step-0c-deferred-placeholder-staleness-behavioural.bats +0 -238
- package/skills/work-problems/test/work-problems-step-0d-outbound-responses-staleness-behavioural.bats +0 -174
- package/skills/work-problems/test/work-problems-step-2-5-routing.bats +0 -80
- package/skills/work-problems/test/work-problems-step-2-5b-cross-halt-routing.bats +0 -149
- package/skills/work-problems/test/work-problems-step-3-5-jtbd-ratification-predicate.bats +0 -191
- package/skills/work-problems/test/work-problems-step-5-bats-polling-discipline.bats +0 -91
- package/skills/work-problems/test/work-problems-step-5-delegation.bats +0 -333
- package/skills/work-problems/test/work-problems-step-5-idle-timeout-sigterm.bats +0 -421
- package/skills/work-problems/test/work-problems-step-5-is-error-transient-halt.bats +0 -278
- package/skills/work-problems/test/work-problems-step-5-iter-changeset-required.bats +0 -95
- package/skills/work-problems/test/work-problems-step-5-prompt-body-re-grounding.bats +0 -128
- package/skills/work-problems/test/work-problems-step-5-stream-timeout-salvage.bats +0 -251
- package/skills/work-problems/test/work-problems-step-6-5-always-drain.bats +0 -203
- package/skills/work-problems/test/work-problems-step-6-5-cache-refresh-chain.bats +0 -174
- package/skills/work-problems/test/work-problems-step-6-5-fix-and-continue.bats +0 -254
- package/skills/work-problems/test/work-problems-step-6-5-postrelease-kv-callback.bats +0 -209
- package/skills/work-problems/test/work-problems-stop-condition-questions.bats +0 -85
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
# Behavioural test: work-problems Step 5 backgrounded-poll-loop dispatch fires
|
|
3
|
-
# SIGTERM on the iteration subprocess when LAST_ACTIVITY_MARK has been stale
|
|
4
|
-
# longer than WORK_PROBLEMS_IDLE_TIMEOUT_S (default 3600s = 60 min). The SIGTERM
|
|
5
|
-
# empirically produces a clean JSON exit-flush per the 2026-04-25 P118 iter 5
|
|
6
|
-
# evidence captured in P121 (and in docs/briefing/afk-subprocess.md).
|
|
7
|
-
#
|
|
8
|
-
# This is the second-source the architect addendum required: the SIGTERM-flushes-
|
|
9
|
-
# JSON evidence is otherwise single-source from one production observation. The
|
|
10
|
-
# fake-claude shim here re-creates the stuck-subprocess shape (emit JSON to stdout,
|
|
11
|
-
# then sleep past the threshold while remaining killable by SIGTERM) and asserts
|
|
12
|
-
# the orchestrator-shape harness's poll-and-sigterm behaviour matches the
|
|
13
|
-
# contract documented in SKILL.md Step 5.
|
|
14
|
-
#
|
|
15
|
-
# @problem P121
|
|
16
|
-
# @jtbd JTBD-006
|
|
17
|
-
# @jtbd JTBD-001
|
|
18
|
-
#
|
|
19
|
-
# Cross-reference:
|
|
20
|
-
# P121 (orchestrator should SIGTERM stuck claude -p subprocesses after idle-
|
|
21
|
-
# timeout — and SIGTERM appears to flush a clean JSON) — driver ticket
|
|
22
|
-
# ADR-032 (governance skill invocation patterns — subprocess-boundary variant
|
|
23
|
-
# amended 2026-04-26 with the backgrounded-poll-loop refinement)
|
|
24
|
-
# ADR-037 (skill testing strategy — behavioural is the default; doc-lint
|
|
25
|
-
# contract assertions are the Permitted Exception)
|
|
26
|
-
# docs/briefing/afk-subprocess.md (P121 entry — the cross-session knowledge
|
|
27
|
-
# index entry this fixture provides empirical second-source for)
|
|
28
|
-
|
|
29
|
-
setup() {
|
|
30
|
-
TEST_TMP="$(mktemp -d)"
|
|
31
|
-
FAKE_BIN="${TEST_TMP}/bin"
|
|
32
|
-
mkdir -p "$FAKE_BIN"
|
|
33
|
-
|
|
34
|
-
# Fake `claude` binary that simulates a stuck iteration subprocess: emits a
|
|
35
|
-
# valid `claude -p --output-format json` envelope to stdout, then sleeps for
|
|
36
|
-
# FAKE_SLEEP_AFTER seconds (default 30s) while trapping SIGTERM. This matches
|
|
37
|
-
# the 2026-04-25 P118 iter 5 shape: subprocess completes its semantic work,
|
|
38
|
-
# then sits in an idle-wait state until SIGTERM unblocks it. The trap exits 0
|
|
39
|
-
# with the JSON already flushed to stdout — same observable as the production
|
|
40
|
-
# CLI behaviour that motivated P121.
|
|
41
|
-
cat > "$FAKE_BIN/claude" <<'FAKE_EOF'
|
|
42
|
-
#!/usr/bin/env bash
|
|
43
|
-
# Test fake for work-problems Step 5 idle-timeout SIGTERM bats fixture.
|
|
44
|
-
# Emits a JSON envelope then sleeps; SIGTERM exits cleanly (JSON already flushed).
|
|
45
|
-
trap 'exit 0' TERM
|
|
46
|
-
printf '%s\n' '{"is_error":false,"result":"ITERATION_SUMMARY\nticket_id: P000\nticket_title: fake\naction: worked\noutcome: investigated\ncommitted: false\nreason: test fixture\nremaining_backlog_count: 0\nnotes: stuck-subprocess simulation","total_cost_usd":0.01,"duration_ms":100,"usage":{"input_tokens":10,"output_tokens":20,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}'
|
|
47
|
-
sleep "${FAKE_SLEEP_AFTER:-30}"
|
|
48
|
-
FAKE_EOF
|
|
49
|
-
chmod +x "$FAKE_BIN/claude"
|
|
50
|
-
export PATH="$FAKE_BIN:$PATH"
|
|
51
|
-
|
|
52
|
-
SKILL_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
53
|
-
SKILL_FILE="${SKILL_DIR}/SKILL.md"
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
teardown() {
|
|
57
|
-
if [ -n "${TEST_TMP:-}" ] && [ -d "$TEST_TMP" ]; then
|
|
58
|
-
rm -rf "$TEST_TMP"
|
|
59
|
-
fi
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
# Faithful re-implementation of SKILL.md Step 5's backgrounded-poll-loop dispatch.
|
|
63
|
-
# Adopters who copy-paste the SKILL.md Step 5 block into their orchestrator
|
|
64
|
-
# should observe the same outcomes this harness does — that's the contract this
|
|
65
|
-
# fixture pins. The harness uses sleep 1 instead of the SKILL.md's sleep 60 so
|
|
66
|
-
# the test wall-clock stays bounded; the LAST_ACTIVITY_MARK math, SIGTERM action,
|
|
67
|
-
# and JSON-after-SIGTERM read are otherwise identical.
|
|
68
|
-
dispatch_with_poll() {
|
|
69
|
-
local json_file="${TEST_TMP}/iter.json"
|
|
70
|
-
local idle_timeout_s="${WORK_PROBLEMS_IDLE_TIMEOUT_S:-3600}"
|
|
71
|
-
local dispatch_start_epoch
|
|
72
|
-
dispatch_start_epoch=$(date +%s)
|
|
73
|
-
local sigterm_sent=0
|
|
74
|
-
|
|
75
|
-
: > "$json_file"
|
|
76
|
-
claude -p --permission-mode bypassPermissions --output-format json "TEST" \
|
|
77
|
-
< /dev/null > "$json_file" 2>&1 &
|
|
78
|
-
local iter_pid=$!
|
|
79
|
-
|
|
80
|
-
while kill -0 "$iter_pid" 2>/dev/null; do
|
|
81
|
-
sleep 1
|
|
82
|
-
local now
|
|
83
|
-
now=$(date +%s)
|
|
84
|
-
# LAST_ACTIVITY_MARK = max(DISPATCH_START, last commit timestamp).
|
|
85
|
-
# In this test there is no git repo and no commits, so the max is just
|
|
86
|
-
# DISPATCH_START — same shape as a real skip-iteration that produces no
|
|
87
|
-
# commit during its run.
|
|
88
|
-
local last_activity_mark=$dispatch_start_epoch
|
|
89
|
-
local idle_seconds=$(( now - last_activity_mark ))
|
|
90
|
-
if (( idle_seconds > idle_timeout_s )) && (( sigterm_sent == 0 )); then
|
|
91
|
-
kill -TERM "$iter_pid" 2>/dev/null || true
|
|
92
|
-
sigterm_sent=1
|
|
93
|
-
fi
|
|
94
|
-
done
|
|
95
|
-
|
|
96
|
-
wait "$iter_pid" 2>/dev/null || true
|
|
97
|
-
|
|
98
|
-
printf 'SIGTERM_SENT=%d\n' "$sigterm_sent"
|
|
99
|
-
printf '%s\n' '---JSON---'
|
|
100
|
-
cat "$json_file"
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
# (a) SIGTERM was sent within the threshold.
|
|
104
|
-
@test "P121: SIGTERM fires when subprocess idle exceeds WORK_PROBLEMS_IDLE_TIMEOUT_S" {
|
|
105
|
-
export FAKE_SLEEP_AFTER=10
|
|
106
|
-
export WORK_PROBLEMS_IDLE_TIMEOUT_S=2
|
|
107
|
-
run dispatch_with_poll
|
|
108
|
-
[ "$status" -eq 0 ]
|
|
109
|
-
[[ "$output" == *"SIGTERM_SENT=1"* ]]
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
# (b) JSON arrives after SIGTERM (clean exit-flush).
|
|
113
|
-
@test "P121: JSON arrives after SIGTERM and parses cleanly (clean exit-flush per 2026-04-25 P118 iter 5)" {
|
|
114
|
-
export FAKE_SLEEP_AFTER=10
|
|
115
|
-
export WORK_PROBLEMS_IDLE_TIMEOUT_S=2
|
|
116
|
-
run dispatch_with_poll
|
|
117
|
-
[ "$status" -eq 0 ]
|
|
118
|
-
[[ "$output" == *"SIGTERM_SENT=1"* ]]
|
|
119
|
-
# Extract the JSON portion after the ---JSON--- marker and validate.
|
|
120
|
-
json_payload=$(printf '%s\n' "$output" | sed -n '/^---JSON---$/,$p' | tail -n +2)
|
|
121
|
-
printf '%s' "$json_payload" | python3 -c '
|
|
122
|
-
import json, sys
|
|
123
|
-
j = json.loads(sys.stdin.read().strip())
|
|
124
|
-
assert not j.get("is_error"), "is_error should be false"
|
|
125
|
-
assert "ITERATION_SUMMARY" in j["result"], "result must carry ITERATION_SUMMARY"
|
|
126
|
-
assert "total_cost_usd" in j, "cost metadata must survive SIGTERM exit-flush"
|
|
127
|
-
'
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
# (c) Env-var override is honoured (default 3600s; override to 2s).
|
|
131
|
-
@test "P121: WORK_PROBLEMS_IDLE_TIMEOUT_S env-var override is honoured" {
|
|
132
|
-
# Without an override, the default 3600s would never fire in test wall-clock,
|
|
133
|
-
# so no SIGTERM. With WORK_PROBLEMS_IDLE_TIMEOUT_S=2, SIGTERM fires within
|
|
134
|
-
# seconds. Confirms the override is consulted by the harness, matching the
|
|
135
|
-
# SKILL.md contract that adopters can tune the threshold per-environment.
|
|
136
|
-
export FAKE_SLEEP_AFTER=10
|
|
137
|
-
export WORK_PROBLEMS_IDLE_TIMEOUT_S=2
|
|
138
|
-
run dispatch_with_poll
|
|
139
|
-
[ "$status" -eq 0 ]
|
|
140
|
-
[[ "$output" == *"SIGTERM_SENT=1"* ]]
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
# (d) Within-threshold runs are NOT SIGTERMed (negative case).
|
|
144
|
-
@test "P121: within-threshold runs are NOT SIGTERMed (subprocess exits before idle threshold)" {
|
|
145
|
-
# Subprocess exits naturally in 1 second; idle timeout is 60s. Loop must
|
|
146
|
-
# observe the natural exit and NOT send SIGTERM. Guards against an over-eager
|
|
147
|
-
# poll loop that would interrupt every iteration regardless of state.
|
|
148
|
-
export FAKE_SLEEP_AFTER=1
|
|
149
|
-
export WORK_PROBLEMS_IDLE_TIMEOUT_S=60
|
|
150
|
-
run dispatch_with_poll
|
|
151
|
-
[ "$status" -eq 0 ]
|
|
152
|
-
[[ "$output" == *"SIGTERM_SENT=0"* ]]
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
# Doc-lint contract assertions — pin SKILL.md prose to the contract this fixture
|
|
156
|
-
# exercises behaviourally. Permitted Exception under ADR-037 (the SKILL.md is
|
|
157
|
-
# the contract document; these assertions guard against silent prose drift away
|
|
158
|
-
# from the behavioural expectation above).
|
|
159
|
-
|
|
160
|
-
@test "P121: SKILL.md Step 5 names WORK_PROBLEMS_IDLE_TIMEOUT_S env var" {
|
|
161
|
-
run grep -nE "WORK_PROBLEMS_IDLE_TIMEOUT_S" "$SKILL_FILE"
|
|
162
|
-
[ "$status" -eq 0 ]
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
@test "P121: SKILL.md Step 5 documents SIGTERM-on-idle action" {
|
|
166
|
-
run grep -niE "SIGTERM.{0,80}idle|idle.{0,80}SIGTERM|kill[[:space:]]+-TERM" "$SKILL_FILE"
|
|
167
|
-
[ "$status" -eq 0 ]
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
@test "P121: SKILL.md Step 5 names LAST_ACTIVITY_MARK signal" {
|
|
171
|
-
run grep -nE "LAST_ACTIVITY_MARK|last activity mark" "$SKILL_FILE"
|
|
172
|
-
[ "$status" -eq 0 ]
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
@test "P121: SKILL.md Step 5 cites P121 (idle-timeout SIGTERM driver)" {
|
|
176
|
-
run grep -nE "P121" "$SKILL_FILE"
|
|
177
|
-
[ "$status" -eq 0 ]
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
@test "P121: SKILL.md Step 5 documents the LAST_ACTIVITY signal trade-off (skip-iteration case)" {
|
|
181
|
-
# Per architect amendment 3: signal trade-off must be explicit so future
|
|
182
|
-
# contributors don't silently re-rate it. The SKILL.md prose names the
|
|
183
|
-
# max(dispatch_start, last commit) shape so adopters know skip iterations
|
|
184
|
-
# are bounded by IDLE_TIMEOUT_S since dispatch start, not a stale commit
|
|
185
|
-
# timestamp.
|
|
186
|
-
run grep -niE "max.{0,40}(dispatch.?start|DISPATCH_START).{0,80}(commit|git log)|skip.?iteration.{0,80}(timeout|threshold|bounded)|dispatch.?start.{0,80}upper.?bound" "$SKILL_FILE"
|
|
187
|
-
[ "$status" -eq 0 ]
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
@test "P121: SKILL.md Step 5 dispatch backgrounds the subprocess (PID capture for poll)" {
|
|
191
|
-
# The dispatch command shape must show the backgrounded form (& + $!) so the
|
|
192
|
-
# poll loop has a PID to kill -0 / kill -TERM. Foreground synchronous
|
|
193
|
-
# dispatch (current pre-P121 shape) cannot support idle-timeout SIGTERM.
|
|
194
|
-
run grep -nE 'ITER_PID=\$!|& *\n*ITER_PID|claude -p.{0,200}&[[:space:]]*$' "$SKILL_FILE"
|
|
195
|
-
[ "$status" -eq 0 ]
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
# ---------------------------------------------------------------------------
|
|
199
|
-
# P147 stuck-before-emit subclass: P121's "SIGTERM produces clean JSON exit-
|
|
200
|
-
# flush" claim was empirically grounded only against subprocesses that had
|
|
201
|
-
# ALREADY emitted ITERATION_SUMMARY before going idle (the P118 evidence). The
|
|
202
|
-
# 2026-04-29 P146 incident falsified the generalisation: a subprocess that
|
|
203
|
-
# deadlocked BEFORE ITERATION_SUMMARY emission produced exit 143 + a 0-byte
|
|
204
|
-
# JSON file when SIGTERMed. `claude -p --output-format json` writes the entire
|
|
205
|
-
# response as a single blob ON normal exit; SIGTERM-before-blob-write means no
|
|
206
|
-
# JSON is ever written.
|
|
207
|
-
#
|
|
208
|
-
# The fixture below exercises the stuck-before-emit shape with a fake `claude`
|
|
209
|
-
# that traps SIGTERM and exits WITHOUT writing any stdout. The orchestrator-
|
|
210
|
-
# shape harness then SIGTERMs after the idle threshold, and the assertions
|
|
211
|
-
# pin: (a) the JSON file is 0 bytes (the metadata-loss-event indicator the
|
|
212
|
-
# SKILL.md prose now warns adopters to watch for), and (b) SIGTERM was sent
|
|
213
|
-
# (the recovery primitive still fires — the bug is in the prose claim about
|
|
214
|
-
# what flushes, not in the SIGTERM action itself).
|
|
215
|
-
#
|
|
216
|
-
# @problem P147
|
|
217
|
-
|
|
218
|
-
dispatch_with_poll_no_emit() {
|
|
219
|
-
local json_file="${TEST_TMP}/iter.json"
|
|
220
|
-
local idle_timeout_s="${WORK_PROBLEMS_IDLE_TIMEOUT_S:-3600}"
|
|
221
|
-
local dispatch_start_epoch
|
|
222
|
-
dispatch_start_epoch=$(date +%s)
|
|
223
|
-
local sigterm_sent=0
|
|
224
|
-
|
|
225
|
-
: > "$json_file"
|
|
226
|
-
claude_no_emit -p --permission-mode bypassPermissions --output-format json "TEST" \
|
|
227
|
-
< /dev/null > "$json_file" 2>&1 &
|
|
228
|
-
local iter_pid=$!
|
|
229
|
-
|
|
230
|
-
while kill -0 "$iter_pid" 2>/dev/null; do
|
|
231
|
-
sleep 1
|
|
232
|
-
local now
|
|
233
|
-
now=$(date +%s)
|
|
234
|
-
local last_activity_mark=$dispatch_start_epoch
|
|
235
|
-
local idle_seconds=$(( now - last_activity_mark ))
|
|
236
|
-
if (( idle_seconds > idle_timeout_s )) && (( sigterm_sent == 0 )); then
|
|
237
|
-
kill -TERM "$iter_pid" 2>/dev/null || true
|
|
238
|
-
sigterm_sent=1
|
|
239
|
-
fi
|
|
240
|
-
done
|
|
241
|
-
|
|
242
|
-
wait "$iter_pid" 2>/dev/null || true
|
|
243
|
-
|
|
244
|
-
local json_bytes
|
|
245
|
-
json_bytes=$(wc -c < "$json_file" | tr -d ' ')
|
|
246
|
-
|
|
247
|
-
printf 'SIGTERM_SENT=%d\n' "$sigterm_sent"
|
|
248
|
-
printf 'JSON_BYTES=%s\n' "$json_bytes"
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
setup_no_emit_shim() {
|
|
252
|
-
cat > "$FAKE_BIN/claude_no_emit" <<'FAKE_EOF'
|
|
253
|
-
#!/usr/bin/env bash
|
|
254
|
-
# Test fake for work-problems Step 5 P147 stuck-before-emit fixture.
|
|
255
|
-
# Traps SIGTERM and exits 0 WITHOUT emitting any stdout. Mirrors the 2026-
|
|
256
|
-
# 04-29 P146 incident shape: subprocess deadlocked before ITERATION_SUMMARY
|
|
257
|
-
# emission; SIGTERM cannot flush a JSON blob the CLI never produced.
|
|
258
|
-
trap 'exit 0' TERM
|
|
259
|
-
sleep "${FAKE_SLEEP_AFTER:-30}"
|
|
260
|
-
FAKE_EOF
|
|
261
|
-
chmod +x "$FAKE_BIN/claude_no_emit"
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
@test "P147: SIGTERM-before-emit produces 0-byte JSON (stuck-before-emit subclass)" {
|
|
265
|
-
setup_no_emit_shim
|
|
266
|
-
export FAKE_SLEEP_AFTER=10
|
|
267
|
-
export WORK_PROBLEMS_IDLE_TIMEOUT_S=2
|
|
268
|
-
run dispatch_with_poll_no_emit
|
|
269
|
-
[ "$status" -eq 0 ]
|
|
270
|
-
[[ "$output" == *"SIGTERM_SENT=1"* ]]
|
|
271
|
-
[[ "$output" == *"JSON_BYTES=0"* ]]
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
@test "P147: SKILL.md Step 5 names the conditional caveat for SIGTERM exit-flush" {
|
|
275
|
-
# The prose claim must NOT generalise the P118 clean-flush observation
|
|
276
|
-
# universally; it must explicitly condition on ITERATION_SUMMARY having been
|
|
277
|
-
# emitted before SIGTERM. Adopters reading the SKILL.md need to know that
|
|
278
|
-
# SIGTERM-before-emit produces a 0-byte JSON, not a clean flush. Require
|
|
279
|
-
# the failure-mode language explicitly so a future drift that removes the
|
|
280
|
-
# caveat but happens to mention ITERATION_SUMMARY in a different sentence
|
|
281
|
-
# cannot keep this assertion green.
|
|
282
|
-
run grep -niE "stuck.?before.?emit|before ITERATION_SUMMARY|ITERATION_SUMMARY.{0,80}not.{0,40}(yet|been).{0,40}emit|conditional.{0,40}(caveat|on)" "$SKILL_FILE"
|
|
283
|
-
[ "$status" -eq 0 ]
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
@test "P147: SKILL.md Step 5 documents metadata-loss-event handling (git-evidence + halt + billing-dashboard)" {
|
|
287
|
-
# When SIGTERM-before-emit is observed (exit 143 + 0-byte JSON), the
|
|
288
|
-
# orchestrator must verify work integrity from independent evidence (git
|
|
289
|
-
# log + git status), halt the AFK loop per exit-code semantics, and
|
|
290
|
-
# reconstruct cost from the Anthropic billing dashboard. This guards
|
|
291
|
-
# against orchestrators silently treating exit-143-no-JSON as a normal
|
|
292
|
-
# iteration completion.
|
|
293
|
-
run grep -niE "metadata.?loss|git log.{0,80}git status|billing dashboard|reconstruct cost" "$SKILL_FILE"
|
|
294
|
-
[ "$status" -eq 0 ]
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
@test "P147: SKILL.md Step 5 cites P147 (conditional-caveat ticket)" {
|
|
298
|
-
run grep -nE "P147" "$SKILL_FILE"
|
|
299
|
-
[ "$status" -eq 0 ]
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
# ---------------------------------------------------------------------------
|
|
303
|
-
# P307 machine-sleep false-kill subclass: P121's IDLE_SECONDS = NOW -
|
|
304
|
-
# LAST_ACTIVITY_MARK computation is wall-clock time. When the host machine
|
|
305
|
-
# suspends/sleeps between the 60s polls, wall-clock advances while the iter
|
|
306
|
-
# subprocess is itself suspended (no actual idle work). On resume, IDLE_SECONDS
|
|
307
|
-
# jumps past the threshold and SIGTERM fires on a subprocess that was
|
|
308
|
-
# genuinely making progress, not stuck. The 2026-05-26 evidence: poll log
|
|
309
|
-
# idle jumped non-linearly 481s -> 1016s -> 5544s across suspend gaps,
|
|
310
|
-
# SIGTERM at idle=5544s > 3600s threshold, exit 143 + 0-byte JSON (the
|
|
311
|
-
# P147 stuck-before-emit metadata-loss class).
|
|
312
|
-
#
|
|
313
|
-
# Fix: detect large wall-clock jumps between consecutive polls (>> 60s
|
|
314
|
-
# expected) as suspend events and shift LAST_ACTIVITY_MARK forward by the
|
|
315
|
-
# gap-minus-expected so IDLE_SECONDS approximates active-elapsed rather
|
|
316
|
-
# than wall-clock-elapsed. Pure-bash heuristic — no monotonic-clock
|
|
317
|
-
# dependency.
|
|
318
|
-
#
|
|
319
|
-
# @problem P307
|
|
320
|
-
|
|
321
|
-
# Pure-bash helper mirroring SKILL.md Step 5 suspend-detect math. Tests
|
|
322
|
-
# below pin the algorithm against parameter combinations exercising
|
|
323
|
-
# (a) normal poll cadence (no shift), (b) within-jitter delay (no shift),
|
|
324
|
-
# (c) detected suspend (shift forward by actual-minus-expected),
|
|
325
|
-
# (d) reproduction of the 2026-05-26 5544s evidence (large shift absorbs
|
|
326
|
-
# the gap). The shape returned is the EFFECTIVE LAST_ACTIVITY_MARK such
|
|
327
|
-
# that IDLE_SECONDS = NOW - effective_mark yields active-elapsed.
|
|
328
|
-
compute_effective_mark() {
|
|
329
|
-
local prev_mark="$1"
|
|
330
|
-
local prev_poll="$2"
|
|
331
|
-
local now="$3"
|
|
332
|
-
local expected_delta="${4:-60}"
|
|
333
|
-
local jitter="${5:-120}"
|
|
334
|
-
|
|
335
|
-
local actual_delta=$(( now - prev_poll ))
|
|
336
|
-
local threshold=$(( expected_delta + jitter ))
|
|
337
|
-
if (( actual_delta > threshold )); then
|
|
338
|
-
printf '%d\n' $(( prev_mark + actual_delta - expected_delta ))
|
|
339
|
-
else
|
|
340
|
-
printf '%d\n' "$prev_mark"
|
|
341
|
-
fi
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
@test "P307: normal poll cadence (60s actual delta) does NOT shift LAST_ACTIVITY_MARK" {
|
|
345
|
-
# 60s between polls is the expected `sleep 60` cadence; no suspend; mark
|
|
346
|
-
# unchanged. Guards against an over-eager heuristic that would shift on
|
|
347
|
-
# every normal poll.
|
|
348
|
-
run compute_effective_mark 1000 0 60 60 120
|
|
349
|
-
[ "$status" -eq 0 ]
|
|
350
|
-
[ "$output" = "1000" ]
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
@test "P307: within-jitter delay (90s actual delta) does NOT shift LAST_ACTIVITY_MARK" {
|
|
354
|
-
# 90s between polls is mild jitter (slow hook, GC pause, brief load
|
|
355
|
-
# spike); below the 60+120=180s suspend threshold; mark unchanged.
|
|
356
|
-
# Bounded noise must not trigger a shift.
|
|
357
|
-
run compute_effective_mark 1000 0 90 60 120
|
|
358
|
-
[ "$status" -eq 0 ]
|
|
359
|
-
[ "$output" = "1000" ]
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
@test "P307: at-threshold delay (180s actual delta) does NOT shift LAST_ACTIVITY_MARK" {
|
|
363
|
-
# Exactly at expected+jitter is the boundary; strict-greater-than test
|
|
364
|
-
# means no shift at the boundary. Adopters tuning the jitter window
|
|
365
|
-
# know 180s == EXPECTED_POLL_DELTA_S + SUSPEND_JITTER_S is the inclusive
|
|
366
|
-
# ceiling of the no-shift band.
|
|
367
|
-
run compute_effective_mark 1000 0 180 60 120
|
|
368
|
-
[ "$status" -eq 0 ]
|
|
369
|
-
[ "$output" = "1000" ]
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
@test "P307: detected suspend (300s actual delta) shifts mark forward by actual-minus-expected" {
|
|
373
|
-
# 300s between polls vastly exceeds the 180s threshold; treat as suspend
|
|
374
|
-
# event and shift mark forward by 300-60=240s. Effect: IDLE_SECONDS
|
|
375
|
-
# (NOW - effective_mark) reads 60s instead of 300s, preserving the
|
|
376
|
-
# subprocess from a wall-clock false-kill.
|
|
377
|
-
run compute_effective_mark 1000 0 300 60 120
|
|
378
|
-
[ "$status" -eq 0 ]
|
|
379
|
-
[ "$output" = "1240" ]
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
@test "P307: reproduces 2026-05-26 iter 1 evidence (5544s suspend gap shifts mark to absorb)" {
|
|
383
|
-
# Concrete reproduction of the production observation: poll saw idle
|
|
384
|
-
# jump to 5544s after a multi-hour laptop suspend. Without suspend-detect,
|
|
385
|
-
# SIGTERM fires at 5544s > 3600s threshold. With suspend-detect, mark
|
|
386
|
-
# shifts forward by 5544-60=5484s; IDLE_SECONDS = 5544 - 5484 = 60s,
|
|
387
|
-
# below threshold; iter survives.
|
|
388
|
-
run compute_effective_mark 0 0 5544 60 120
|
|
389
|
-
[ "$status" -eq 0 ]
|
|
390
|
-
[ "$output" = "5484" ]
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
@test "P307: SKILL.md Step 5 documents the suspend-detect heuristic" {
|
|
394
|
-
# Prose must name the heuristic so adopters reading the SKILL.md know
|
|
395
|
-
# how the poll loop survives machine-sleep without inventing one.
|
|
396
|
-
# Accept any of: "suspend-detect", "wall-clock jump", "machine sleep",
|
|
397
|
-
# "machine-sleep", or the constants EXPECTED_POLL_DELTA_S /
|
|
398
|
-
# SUSPEND_JITTER_S / SUSPEND_OFFSET_S that name the construct.
|
|
399
|
-
run grep -niE "suspend.?detect|wall.?clock jump|machine.?sleep|EXPECTED_POLL_DELTA_S|SUSPEND_JITTER_S|SUSPEND_OFFSET_S" "$SKILL_FILE"
|
|
400
|
-
[ "$status" -eq 0 ]
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
@test "P307: SKILL.md Step 5 cites P307 (machine-sleep false-kill driver)" {
|
|
404
|
-
run grep -nE "P307" "$SKILL_FILE"
|
|
405
|
-
[ "$status" -eq 0 ]
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
@test "P307: SKILL.md Step 5 trade-off paragraph names suspend-detect alongside skip-iter trade-off" {
|
|
409
|
-
# The LAST_ACTIVITY_MARK signal trade-off paragraph (existing at L410)
|
|
410
|
-
# enumerates alternatives considered and rejected (mtime, RSS). The
|
|
411
|
-
# suspend-detect addition belongs in the same locus per architect
|
|
412
|
-
# review — keeps the rationale chain (P121 -> P147 -> trade-off ->
|
|
413
|
-
# P307 suspend-detect) reading linearly rather than fragmenting into
|
|
414
|
-
# a separate section. Assert the trade-off paragraph names both
|
|
415
|
-
# SUSPEND_OFFSET_S (the accumulator) AND the EXPECTED_POLL_DELTA_S +
|
|
416
|
-
# SUSPEND_JITTER_S threshold so the rationale chain is complete.
|
|
417
|
-
run grep -nE "LAST_ACTIVITY_MARK signal trade-off" "$SKILL_FILE"
|
|
418
|
-
[ "$status" -eq 0 ]
|
|
419
|
-
run grep -niE "SUSPEND_OFFSET_S|suspend.?offset" "$SKILL_FILE"
|
|
420
|
-
[ "$status" -eq 0 ]
|
|
421
|
-
}
|