@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,144 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# P065 / ADR-036: pre-publish-intake-gate.sh PreToolUse:Bash hook must deny
|
|
4
|
-
# `npm publish` (and changesets-release `gh pr merge`) when the four intake
|
|
5
|
-
# files are missing, unless the project has opted out via the decline
|
|
6
|
-
# marker (`.claude/.intake-scaffold-declined`) or the user sets the
|
|
7
|
-
# `INTAKE_BYPASS=1` env override.
|
|
8
|
-
#
|
|
9
|
-
# Required intake files (per ADR-036 Detection step 5):
|
|
10
|
-
# .github/ISSUE_TEMPLATE/config.yml
|
|
11
|
-
# .github/ISSUE_TEMPLATE/problem-report.yml
|
|
12
|
-
# SECURITY.md
|
|
13
|
-
# SUPPORT.md
|
|
14
|
-
# CONTRIBUTING.md
|
|
15
|
-
#
|
|
16
|
-
# Per feedback_behavioural_tests.md (P081): behavioural assertions —
|
|
17
|
-
# simulate the hook's payload on stdin and assert on emitted JSON
|
|
18
|
-
# permissionDecision and exit status. No source-grep on hook content.
|
|
19
|
-
|
|
20
|
-
setup() {
|
|
21
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
22
|
-
HOOK="$SCRIPT_DIR/pre-publish-intake-gate.sh"
|
|
23
|
-
ORIG_DIR="$PWD"
|
|
24
|
-
TEST_DIR=$(mktemp -d)
|
|
25
|
-
cd "$TEST_DIR"
|
|
26
|
-
unset INTAKE_BYPASS
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
teardown() {
|
|
30
|
-
cd "$ORIG_DIR"
|
|
31
|
-
rm -rf "$TEST_DIR"
|
|
32
|
-
unset INTAKE_BYPASS
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
# Helper: simulate the PreToolUse:Bash payload on stdin.
|
|
36
|
-
run_bash_hook() {
|
|
37
|
-
local cmd="$1"
|
|
38
|
-
local json
|
|
39
|
-
json=$(printf '{"tool_name":"Bash","tool_input":{"command":"%s"}}' "$cmd")
|
|
40
|
-
echo "$json" | bash "$HOOK"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
# Helper: scaffold all five intake files in the test directory.
|
|
44
|
-
scaffold_all_intake() {
|
|
45
|
-
mkdir -p .github/ISSUE_TEMPLATE
|
|
46
|
-
echo "blank_issues_enabled: false" > .github/ISSUE_TEMPLATE/config.yml
|
|
47
|
-
echo "name: Report a problem" > .github/ISSUE_TEMPLATE/problem-report.yml
|
|
48
|
-
echo "# Security Policy" > SECURITY.md
|
|
49
|
-
echo "# Getting Help" > SUPPORT.md
|
|
50
|
-
echo "# Contributing" > CONTRIBUTING.md
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
# Helper: set the decline marker.
|
|
54
|
-
decline() {
|
|
55
|
-
mkdir -p .claude
|
|
56
|
-
: > .claude/.intake-scaffold-declined
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
# --- Allow paths ---
|
|
60
|
-
|
|
61
|
-
@test "allow: npm publish proceeds when all five intake files are present" {
|
|
62
|
-
scaffold_all_intake
|
|
63
|
-
run run_bash_hook "npm publish"
|
|
64
|
-
[ "$status" -eq 0 ]
|
|
65
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@test "allow: missing intake but decline marker present permits publish" {
|
|
69
|
-
decline
|
|
70
|
-
run run_bash_hook "npm publish"
|
|
71
|
-
[ "$status" -eq 0 ]
|
|
72
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
@test "allow: missing intake but INTAKE_BYPASS=1 permits publish (checked BEFORE existence)" {
|
|
76
|
-
# Architect direction: INTAKE_BYPASS must short-circuit before the
|
|
77
|
-
# existence check fires.
|
|
78
|
-
INTAKE_BYPASS=1 run run_bash_hook "npm publish"
|
|
79
|
-
[ "$status" -eq 0 ]
|
|
80
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
@test "allow: non-publish bash commands are out of scope" {
|
|
84
|
-
run run_bash_hook "ls -la"
|
|
85
|
-
[ "$status" -eq 0 ]
|
|
86
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
87
|
-
run run_bash_hook "git status"
|
|
88
|
-
[ "$status" -eq 0 ]
|
|
89
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
@test "allow: non-Bash tool calls are out of scope" {
|
|
93
|
-
json='{"tool_name":"Read","tool_input":{"file_path":"foo"}}'
|
|
94
|
-
run bash -c "echo '$json' | bash $HOOK"
|
|
95
|
-
[ "$status" -eq 0 ]
|
|
96
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
# --- Deny paths ---
|
|
100
|
-
|
|
101
|
-
@test "deny: npm publish with no intake files, no marker, no bypass" {
|
|
102
|
-
run run_bash_hook "npm publish"
|
|
103
|
-
[ "$status" -eq 0 ]
|
|
104
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
105
|
-
[[ "$output" == *"intake"* ]]
|
|
106
|
-
[[ "$output" == *"scaffold-intake"* ]]
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
@test "deny: npm publish with partial intake (3 of 5) still denies" {
|
|
110
|
-
mkdir -p .github/ISSUE_TEMPLATE
|
|
111
|
-
echo "blank_issues_enabled: false" > .github/ISSUE_TEMPLATE/config.yml
|
|
112
|
-
echo "name: Report a problem" > .github/ISSUE_TEMPLATE/problem-report.yml
|
|
113
|
-
echo "# Security Policy" > SECURITY.md
|
|
114
|
-
# SUPPORT.md and CONTRIBUTING.md missing
|
|
115
|
-
run run_bash_hook "npm publish"
|
|
116
|
-
[ "$status" -eq 0 ]
|
|
117
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
@test "deny message names the recovery affordances (skill, marker, bypass)" {
|
|
121
|
-
run run_bash_hook "npm publish"
|
|
122
|
-
[ "$status" -eq 0 ]
|
|
123
|
-
[[ "$output" == *"scaffold-intake"* ]]
|
|
124
|
-
[[ "$output" == *"INTAKE_BYPASS"* ]]
|
|
125
|
-
[[ "$output" == *".intake-scaffold-declined"* ]]
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
# --- gh pr merge on changeset-release/* ---
|
|
129
|
-
|
|
130
|
-
@test "deny: gh pr merge of a changeset-release/* PR with missing intake" {
|
|
131
|
-
# ADR-036 Trigger 2 also matches gh pr merge against changeset-release/*
|
|
132
|
-
# branches (the changesets release-PR pattern).
|
|
133
|
-
run run_bash_hook "gh pr merge 42 --repo windyroad/example --branch changeset-release/main"
|
|
134
|
-
[ "$status" -eq 0 ]
|
|
135
|
-
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@test "allow: gh pr merge of a non-changeset PR is out of scope" {
|
|
139
|
-
# Only the changeset-release/* branch pattern is in scope; a regular
|
|
140
|
-
# feature-branch merge does not flip the publish boundary.
|
|
141
|
-
run run_bash_hook "gh pr merge 99 --branch feature/foo"
|
|
142
|
-
[ "$status" -eq 0 ]
|
|
143
|
-
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
144
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# P142 / ADR-050: itil-runtime-sid-marker.sh PreToolUse hook.
|
|
4
|
-
#
|
|
5
|
-
# Behavioural contract:
|
|
6
|
-
# 1. Hook receives JSON on stdin with a `session_id` field.
|
|
7
|
-
# 2. Hook writes the session_id to the runtime-SID marker path
|
|
8
|
-
# (computed by `runtime_sid_path()` in lib/runtime-sid.sh).
|
|
9
|
-
# 3. Hook emits 0 bytes on stdout (ADR-045 Pattern 1: side-effect-only,
|
|
10
|
-
# silent-on-pass — no context budget burn per tool call).
|
|
11
|
-
# 4. Hook always exits 0 (fail-open — never block a tool call on
|
|
12
|
-
# marker write).
|
|
13
|
-
# 5. Empty session_id -> hook is a no-op (marker not touched).
|
|
14
|
-
# 6. Subsequent invocations OVERWRITE the marker (so a subprocess
|
|
15
|
-
# tool call replaces the orchestrator's SID with the subprocess's
|
|
16
|
-
# SID for the duration of the subprocess; the orchestrator's
|
|
17
|
-
# next tool call after subprocess exit overwrites it back).
|
|
18
|
-
#
|
|
19
|
-
# Per feedback_behavioural_tests.md (P081): tests assert the hook's
|
|
20
|
-
# observable effects (marker contents, stdout bytes, exit code) — NOT
|
|
21
|
-
# the source content of the hook script.
|
|
22
|
-
|
|
23
|
-
setup() {
|
|
24
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
25
|
-
HOOK="$SCRIPT_DIR/itil-runtime-sid-marker.sh"
|
|
26
|
-
SANDBOX_TMP=$(mktemp -d)
|
|
27
|
-
export SESSION_MARKER_DIR="$SANDBOX_TMP"
|
|
28
|
-
MARKER_PATH="$SANDBOX_TMP/itil-runtime-sid.current"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
teardown() {
|
|
32
|
-
rm -rf "$SANDBOX_TMP"
|
|
33
|
-
unset SESSION_MARKER_DIR
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
# Helper: invoke the hook with a JSON stdin payload.
|
|
37
|
-
fire_hook() {
|
|
38
|
-
local json="$1"
|
|
39
|
-
echo "$json" | bash "$HOOK"
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
@test "hook writes session_id to runtime-SID marker" {
|
|
43
|
-
expected_uuid="aaaaaaaa-1111-2222-3333-444444444444"
|
|
44
|
-
fire_hook "{\"session_id\":\"$expected_uuid\",\"tool_name\":\"Bash\"}"
|
|
45
|
-
[ -f "$MARKER_PATH" ]
|
|
46
|
-
[ "$(cat "$MARKER_PATH")" = "$expected_uuid" ]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@test "hook is silent on stdout (ADR-045 Pattern 1)" {
|
|
50
|
-
expected_uuid="bbbbbbbb-1111-2222-3333-444444444444"
|
|
51
|
-
output=$(fire_hook "{\"session_id\":\"$expected_uuid\",\"tool_name\":\"Bash\"}")
|
|
52
|
-
[ -z "$output" ]
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@test "hook exits 0 on success" {
|
|
56
|
-
expected_uuid="cccccccc-1111-2222-3333-444444444444"
|
|
57
|
-
echo "{\"session_id\":\"$expected_uuid\",\"tool_name\":\"Bash\"}" | bash "$HOOK"
|
|
58
|
-
[ "$?" -eq 0 ]
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@test "hook overwrites prior marker on subsequent invocation" {
|
|
62
|
-
first_uuid="dddddddd-1111-2222-3333-444444444444"
|
|
63
|
-
second_uuid="eeeeeeee-1111-2222-3333-444444444444"
|
|
64
|
-
fire_hook "{\"session_id\":\"$first_uuid\",\"tool_name\":\"Bash\"}"
|
|
65
|
-
[ "$(cat "$MARKER_PATH")" = "$first_uuid" ]
|
|
66
|
-
fire_hook "{\"session_id\":\"$second_uuid\",\"tool_name\":\"Write\"}"
|
|
67
|
-
[ "$(cat "$MARKER_PATH")" = "$second_uuid" ]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@test "hook is a no-op when session_id is empty" {
|
|
71
|
-
fire_hook "{\"tool_name\":\"Bash\"}"
|
|
72
|
-
[ ! -f "$MARKER_PATH" ]
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
@test "hook is a no-op when stdin is not valid JSON" {
|
|
76
|
-
echo "not-json-at-all" | bash "$HOOK"
|
|
77
|
-
[ "$?" -eq 0 ]
|
|
78
|
-
[ ! -f "$MARKER_PATH" ]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@test "hook fail-open on jq absent (graceful degradation)" {
|
|
82
|
-
# Simulate jq absent by making PATH not include any jq binary.
|
|
83
|
-
expected_uuid="ffffffff-1111-2222-3333-444444444444"
|
|
84
|
-
result=$(echo "{\"session_id\":\"$expected_uuid\",\"tool_name\":\"Bash\"}" | env PATH="/usr/bin:/bin" bash "$HOOK"; echo "EXIT:$?")
|
|
85
|
-
# Either jq is in /usr/bin (fine — marker written), or it's absent
|
|
86
|
-
# (hook should still exit 0 without crashing). The exit-0 contract
|
|
87
|
-
# is the load-bearing assertion; marker presence is a bonus when
|
|
88
|
-
# jq is available.
|
|
89
|
-
[[ "$result" == *"EXIT:0"* ]]
|
|
90
|
-
}
|
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bats
|
|
2
|
-
|
|
3
|
-
# P124: session-id.sh helper must canonicalise per-session UUID discovery
|
|
4
|
-
# for agent-side code paths (e.g. /wr-itil:manage-problem Step 2 substep 7
|
|
5
|
-
# marker write) that today fall back to the brittle ${CLAUDE_SESSION_ID:-default}
|
|
6
|
-
# pattern when the env var is not exported in the agent's process.
|
|
7
|
-
#
|
|
8
|
-
# Behavioural contract:
|
|
9
|
-
# 1. CLAUDE_SESSION_ID exported -> echo it (env-var fast path).
|
|
10
|
-
# 2. CLAUDE_SESSION_ID absent + an /tmp/<system>-announced-<UUID> marker
|
|
11
|
-
# present -> echo the UUID parsed from the marker filename.
|
|
12
|
-
# 3. CLAUDE_SESSION_ID absent + no markers anywhere -> echo nothing,
|
|
13
|
-
# exit non-zero (so callers can detect "could not discover").
|
|
14
|
-
# 4. Multiple announce markers across systems -> deterministic selection
|
|
15
|
-
# (architect first, then jtbd, then tdd, then itil-assistant-gate,
|
|
16
|
-
# then itil-correction-detect, then style-guide, then voice-tone)
|
|
17
|
-
# so the discovery is reproducible across invocations.
|
|
18
|
-
#
|
|
19
|
-
# Per feedback_behavioural_tests.md (P081): tests assert the helper's
|
|
20
|
-
# emitted output and exit code, not the source content of the helper.
|
|
21
|
-
# The marker-system selection order is asserted by constructing only the
|
|
22
|
-
# higher-priority marker and checking the helper picks it.
|
|
23
|
-
|
|
24
|
-
setup() {
|
|
25
|
-
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
26
|
-
HELPER="$SCRIPT_DIR/lib/session-id.sh"
|
|
27
|
-
# Use a sandbox /tmp so we never leak across real session markers.
|
|
28
|
-
SANDBOX_TMP=$(mktemp -d)
|
|
29
|
-
# session-id.sh reads from /tmp by default; SESSION_MARKER_DIR
|
|
30
|
-
# overrides that for sandboxed bats runs without touching real
|
|
31
|
-
# session state in /tmp.
|
|
32
|
-
export SESSION_MARKER_DIR="$SANDBOX_TMP"
|
|
33
|
-
unset CLAUDE_SESSION_ID
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
teardown() {
|
|
37
|
-
rm -rf "$SANDBOX_TMP"
|
|
38
|
-
unset SESSION_MARKER_DIR
|
|
39
|
-
unset CLAUDE_SESSION_ID
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
# Helper: source the helper and emit the discovered SID + exit code.
|
|
43
|
-
discover() {
|
|
44
|
-
bash -c "source '$HELPER'; get_current_session_id; echo \"EXIT:\$?\""
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
# Helper: write an announce marker with a known UUID under SESSION_MARKER_DIR.
|
|
48
|
-
mark_announced() {
|
|
49
|
-
local system="$1"
|
|
50
|
-
local uuid="$2"
|
|
51
|
-
: > "$SESSION_MARKER_DIR/${system}-announced-${uuid}"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
# --- Behavioural contract: env-var fast path ---
|
|
55
|
-
|
|
56
|
-
@test "env-var present -> returns the env-var value verbatim" {
|
|
57
|
-
expected_uuid="11111111-1111-1111-1111-111111111111"
|
|
58
|
-
output=$(CLAUDE_SESSION_ID="$expected_uuid" bash -c "source '$HELPER'; get_current_session_id; echo \"EXIT:\$?\"")
|
|
59
|
-
[[ "$output" == *"$expected_uuid"* ]]
|
|
60
|
-
[[ "$output" == *"EXIT:0"* ]]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
@test "env-var present -> ignores any markers (no scrape)" {
|
|
64
|
-
expected_uuid="22222222-2222-2222-2222-222222222222"
|
|
65
|
-
decoy_uuid="33333333-3333-3333-3333-333333333333"
|
|
66
|
-
mark_announced "architect" "$decoy_uuid"
|
|
67
|
-
output=$(CLAUDE_SESSION_ID="$expected_uuid" bash -c "source '$HELPER'; get_current_session_id; echo \"EXIT:\$?\"")
|
|
68
|
-
[[ "$output" == *"$expected_uuid"* ]]
|
|
69
|
-
[[ "$output" != *"$decoy_uuid"* ]]
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
# --- Behavioural contract: marker-scrape fallback ---
|
|
73
|
-
|
|
74
|
-
@test "env-var absent + architect-announced marker present -> returns marker UUID" {
|
|
75
|
-
expected_uuid="44444444-4444-4444-4444-444444444444"
|
|
76
|
-
mark_announced "architect" "$expected_uuid"
|
|
77
|
-
output=$(discover)
|
|
78
|
-
[[ "$output" == *"$expected_uuid"* ]]
|
|
79
|
-
[[ "$output" == *"EXIT:0"* ]]
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@test "env-var absent + only jtbd-announced marker present -> returns marker UUID (fallback chain works)" {
|
|
83
|
-
expected_uuid="55555555-5555-5555-5555-555555555555"
|
|
84
|
-
mark_announced "jtbd" "$expected_uuid"
|
|
85
|
-
output=$(discover)
|
|
86
|
-
[[ "$output" == *"$expected_uuid"* ]]
|
|
87
|
-
[[ "$output" == *"EXIT:0"* ]]
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
@test "env-var absent + no markers anywhere -> empty output, non-zero exit" {
|
|
91
|
-
output=$(discover)
|
|
92
|
-
# Output should be just "EXIT:<non-zero>" with no UUID before it.
|
|
93
|
-
[[ "$output" =~ ^EXIT:[1-9] ]]
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@test "deterministic priority: architect-announced beats jtbd-announced when both present" {
|
|
97
|
-
architect_uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
|
|
98
|
-
jtbd_uuid="bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
|
|
99
|
-
mark_announced "jtbd" "$jtbd_uuid"
|
|
100
|
-
# Sleep so jtbd marker has older mtime — proves selection is by
|
|
101
|
-
# marker-system priority, not by mtime (the architect-flagged
|
|
102
|
-
# mtime fragility from session 2026-04-26 review).
|
|
103
|
-
sleep 1
|
|
104
|
-
mark_announced "architect" "$architect_uuid"
|
|
105
|
-
output=$(discover)
|
|
106
|
-
[[ "$output" == *"$architect_uuid"* ]]
|
|
107
|
-
[[ "$output" != *"$jtbd_uuid"* ]]
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
# --- Behavioural contract: shell portability (P124 Phase 2) ---
|
|
111
|
-
#
|
|
112
|
-
# The helper is sourced by `/wr-itil:manage-problem` Step 2 substep 7 from
|
|
113
|
-
# whichever shell the agent runs Bash tool calls under. On macOS that is
|
|
114
|
-
# zsh by default; the Phase 1 implementation used `shopt -s nullglob` (a
|
|
115
|
-
# bash builtin), which under zsh errors with `shopt: command not found`
|
|
116
|
-
# and lets the subshell glob expression fall through to a literal
|
|
117
|
-
# unmatched-pattern string. The helper then returned a stale or wrong
|
|
118
|
-
# UUID — defeating its stated guarantee. Citations: P124 ticket
|
|
119
|
-
# "Regression Evidence (2026-04-27)" — main-turn P130 capture, line 119:
|
|
120
|
-
# `get_current_session_id:33: command not found: shopt`.
|
|
121
|
-
#
|
|
122
|
-
# This test asserts the helper works identically under zsh as under bash:
|
|
123
|
-
# same UUID returned, exit 0, no `shopt: command not found` error on
|
|
124
|
-
# stderr.
|
|
125
|
-
|
|
126
|
-
@test "zsh portability: helper returns the same UUID under zsh -c as under bash -c" {
|
|
127
|
-
if ! command -v zsh >/dev/null 2>&1; then
|
|
128
|
-
skip "zsh not available"
|
|
129
|
-
fi
|
|
130
|
-
expected_uuid="cccccccc-cccc-cccc-cccc-cccccccccccc"
|
|
131
|
-
mark_announced "architect" "$expected_uuid"
|
|
132
|
-
zsh_run=$(SESSION_MARKER_DIR="$SANDBOX_TMP" zsh -c "source '$HELPER'; get_current_session_id; echo \"EXIT:\$?\"" 2>&1)
|
|
133
|
-
[[ "$zsh_run" == *"$expected_uuid"* ]]
|
|
134
|
-
[[ "$zsh_run" == *"EXIT:0"* ]]
|
|
135
|
-
[[ "$zsh_run" != *"shopt: command not found"* ]]
|
|
136
|
-
[[ "$zsh_run" != *"command not found: shopt"* ]]
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
# --- Behavioural contract: within-system mtime selection (P124 Phase 3) ---
|
|
140
|
-
#
|
|
141
|
-
# Phase 2 portability fix (shopt-portable for-loop existence check) shipped
|
|
142
|
-
# 2026-04-28 morning, restoring zsh compatibility but introducing a
|
|
143
|
-
# regression on multi-session developer machines: glob expansion enumerates
|
|
144
|
-
# matches in ASCII-alphabetical order, and the inner "first match wins"
|
|
145
|
-
# heuristic returned the lexically-first stale UUID when /tmp had
|
|
146
|
-
# accumulated `${system}-announced-*` markers from prior sessions. Ticket
|
|
147
|
-
# regression block cites 103 stale architect markers selecting the wrong
|
|
148
|
-
# UUID, denying the create-gate (P119) and forcing a brute-force recovery.
|
|
149
|
-
#
|
|
150
|
-
# Phase 3 selection contract: WITHIN a single system's marker namespace,
|
|
151
|
-
# the most-recent-mtime marker wins. ACROSS systems, the fixed priority
|
|
152
|
-
# order is preserved (asserted by the existing "deterministic priority"
|
|
153
|
-
# test above). `-announced-` markers are write-once-per-session per ADR-038
|
|
154
|
-
# (no `touch`-refresh sliding TTL), so mtime IS the announcing session's
|
|
155
|
-
# first-prompt timestamp — newest mtime unambiguously identifies the live
|
|
156
|
-
# session within that system's marker glob.
|
|
157
|
-
|
|
158
|
-
@test "within-system mtime: newest architect-announced marker wins over older same-system markers" {
|
|
159
|
-
oldest_uuid="00000000-0000-0000-0000-000000000001" # ASCII-first
|
|
160
|
-
middle_uuid="11111111-1111-1111-1111-111111111111"
|
|
161
|
-
newest_uuid="22222222-2222-2222-2222-222222222222" # ASCII-last of the three
|
|
162
|
-
# Fixture intent: alphabetical-first UUID has the OLDEST mtime; the
|
|
163
|
-
# newest-mtime UUID is alphabetically last. Phase 2 first-glob-match
|
|
164
|
-
# would pick `oldest_uuid` (alphabetical first); Phase 3 mtime
|
|
165
|
-
# selection picks `newest_uuid` (most-recent mtime). The sleep
|
|
166
|
-
# boundaries guarantee distinct mtimes across filesystems with
|
|
167
|
-
# 1-second mtime resolution (e.g. older HFS+).
|
|
168
|
-
mark_announced "architect" "$oldest_uuid"
|
|
169
|
-
sleep 1
|
|
170
|
-
mark_announced "architect" "$middle_uuid"
|
|
171
|
-
sleep 1
|
|
172
|
-
mark_announced "architect" "$newest_uuid"
|
|
173
|
-
output=$(discover)
|
|
174
|
-
[[ "$output" == *"$newest_uuid"* ]]
|
|
175
|
-
[[ "$output" != *"$oldest_uuid"* ]]
|
|
176
|
-
[[ "$output" != *"$middle_uuid"* ]]
|
|
177
|
-
[[ "$output" == *"EXIT:0"* ]]
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
# --- Behavioural contract: runtime-SID marker (P142 Phase 4) ---
|
|
181
|
-
#
|
|
182
|
-
# Phase 3 mtime-based within-system selection introduced a regression
|
|
183
|
-
# in orchestrator main turns AFTER subprocess dispatch: subprocess
|
|
184
|
-
# announce markers have NEWER mtime than the orchestrator's, so the
|
|
185
|
-
# helper picked the subprocess SID while the runtime hook stdin still
|
|
186
|
-
# contained the orchestrator SID — marker landed under the wrong UUID,
|
|
187
|
-
# create-gate (P119) denied. Mirror failure mode would fire in
|
|
188
|
-
# subprocess context if the priority list were re-ordered to favour
|
|
189
|
-
# orchestrator-only announce systems (no pure-helper algorithm can
|
|
190
|
-
# distinguish "running in orchestrator main turn" from "running in
|
|
191
|
-
# subprocess" by filesystem state alone).
|
|
192
|
-
#
|
|
193
|
-
# Phase 4 structural fix: a new PreToolUse hook
|
|
194
|
-
# (`itil-runtime-sid-marker.sh`) writes the runtime stdin session_id
|
|
195
|
-
# to a per-machine marker on every tool call. The helper reads this
|
|
196
|
-
# marker FIRST as the authoritative current-session SID, falling back
|
|
197
|
-
# to the existing announce-marker priority logic when the marker is
|
|
198
|
-
# absent (cold path — first tool call of a session, before any
|
|
199
|
-
# PreToolUse fires).
|
|
200
|
-
#
|
|
201
|
-
# Sandbox path: when SESSION_MARKER_DIR is set (test override), the
|
|
202
|
-
# runtime marker lives at `${SESSION_MARKER_DIR}/itil-runtime-sid.current`
|
|
203
|
-
# — a single fixed filename, no per-user/per-project scoping. The
|
|
204
|
-
# scoping suffix used in prod (`-${USER}-${proj_hash}`) is irrelevant
|
|
205
|
-
# under sandbox because every test gets a fresh SANDBOX_TMP.
|
|
206
|
-
|
|
207
|
-
@test "runtime-SID marker present: helper returns marker contents over newer announce markers" {
|
|
208
|
-
runtime_uuid="dddddddd-dddd-dddd-dddd-dddddddddddd"
|
|
209
|
-
decoy_uuid="eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"
|
|
210
|
-
# Decoy: an architect-announced marker with a NEWER mtime than the
|
|
211
|
-
# runtime marker. The Phase 3 helper would have picked decoy_uuid;
|
|
212
|
-
# the Phase 4 helper picks runtime_uuid because the runtime-SID
|
|
213
|
-
# marker is authoritative.
|
|
214
|
-
printf '%s' "$runtime_uuid" > "$SANDBOX_TMP/itil-runtime-sid.current"
|
|
215
|
-
sleep 1
|
|
216
|
-
mark_announced "architect" "$decoy_uuid"
|
|
217
|
-
output=$(discover)
|
|
218
|
-
[[ "$output" == *"$runtime_uuid"* ]]
|
|
219
|
-
[[ "$output" != *"$decoy_uuid"* ]]
|
|
220
|
-
[[ "$output" == *"EXIT:0"* ]]
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
@test "runtime-SID marker empty: helper falls back to announce-marker priority" {
|
|
224
|
-
expected_uuid="ffffffff-ffff-ffff-ffff-ffffffffffff"
|
|
225
|
-
mark_announced "architect" "$expected_uuid"
|
|
226
|
-
# Empty runtime marker (zero-byte file) — helper must NOT return
|
|
227
|
-
# the empty contents; it must fall through to the announce-marker
|
|
228
|
-
# scrape. Empty marker can occur if the hook ran with empty
|
|
229
|
-
# session_id stdin (test harness, hook self-test) and would still
|
|
230
|
-
# leave the file at zero bytes per the hook's empty-input fail-open.
|
|
231
|
-
: > "$SANDBOX_TMP/itil-runtime-sid.current"
|
|
232
|
-
output=$(discover)
|
|
233
|
-
[[ "$output" == *"$expected_uuid"* ]]
|
|
234
|
-
[[ "$output" == *"EXIT:0"* ]]
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
@test "runtime-SID marker absent (cold path): helper uses announce-marker priority unchanged" {
|
|
238
|
-
expected_uuid="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
|
239
|
-
mark_announced "architect" "$expected_uuid"
|
|
240
|
-
# No runtime marker created — cold path. Helper falls back to
|
|
241
|
-
# existing Phase 3 announce-marker priority. This test pins the
|
|
242
|
-
# backwards-compat contract: sessions whose first tool call hasn't
|
|
243
|
-
# yet fired the PreToolUse hook still discover their SID via the
|
|
244
|
-
# announce-marker fallback (the priority list is preserved as-is).
|
|
245
|
-
output=$(discover)
|
|
246
|
-
[[ "$output" == *"$expected_uuid"* ]]
|
|
247
|
-
[[ "$output" == *"EXIT:0"* ]]
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
@test "env var beats runtime-SID marker (env-var fast path preserved)" {
|
|
251
|
-
env_uuid="11111111-aaaa-bbbb-cccc-222222222222"
|
|
252
|
-
marker_uuid="22222222-aaaa-bbbb-cccc-333333333333"
|
|
253
|
-
printf '%s' "$marker_uuid" > "$SANDBOX_TMP/itil-runtime-sid.current"
|
|
254
|
-
output=$(CLAUDE_SESSION_ID="$env_uuid" SESSION_MARKER_DIR="$SANDBOX_TMP" bash -c "source '$HELPER'; get_current_session_id; echo \"EXIT:\$?\"")
|
|
255
|
-
[[ "$output" == *"$env_uuid"* ]]
|
|
256
|
-
[[ "$output" != *"$marker_uuid"* ]]
|
|
257
|
-
[[ "$output" == *"EXIT:0"* ]]
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
# --- Behavioural contract: candidate enumeration (Option C, P260) ---
|
|
261
|
-
#
|
|
262
|
-
# Under concurrent orchestrator+subprocess sessions in the SAME project,
|
|
263
|
-
# the per-machine runtime-sid marker is last-writer-wins, so the single-SID
|
|
264
|
-
# get_current_session_id cannot reliably predict which SID the create-gate
|
|
265
|
-
# hook will read from the Write's stdin (the orchestrator's Write carries
|
|
266
|
-
# the orchestrator SID; the subprocess may have just clobbered the runtime
|
|
267
|
-
# marker with its own SID). No agent-side algorithm can PREDICT the right
|
|
268
|
-
# single SID from filesystem state alone (ADR-050 §Context). Option C stops
|
|
269
|
-
# predicting: get_candidate_session_ids returns EVERY recent candidate SID
|
|
270
|
-
# — the get_current_session_id pick PLUS every recent announce-marker UUID
|
|
271
|
-
# across all systems within the mtime window, deduplicated — so the marker
|
|
272
|
-
# can be written under all of them and a match provably exists whichever
|
|
273
|
-
# SID the hook reads. Bounded by SESSION_CANDIDATE_WINDOW_MINS (24h default)
|
|
274
|
-
# to avoid the P124 stale-marker pathology (103 accumulated UUIDs); NOT a
|
|
275
|
-
# global fail-open (the P119 audit invariant holds: every marker still
|
|
276
|
-
# records that THIS session ran the duplicate-check grep).
|
|
277
|
-
#
|
|
278
|
-
# Per feedback_behavioural_tests.md (P081): tests assert the emitted
|
|
279
|
-
# candidate set, not the source content of the helper.
|
|
280
|
-
|
|
281
|
-
# Helper: source the helper and emit the candidate SID set (one per line).
|
|
282
|
-
candidates() {
|
|
283
|
-
bash -c "source '$HELPER'; get_candidate_session_ids"
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
@test "candidates: returns BOTH the orchestrator and subprocess announce-marker UUIDs" {
|
|
287
|
-
orch_uuid="aaaaaaaa-0000-1111-2222-orchestrator0"
|
|
288
|
-
sub_uuid="bbbbbbbb-0000-1111-2222-subprocess00"
|
|
289
|
-
# Orchestrator announced first (loop start); subprocess announced later
|
|
290
|
-
# (dispatched mid-loop) — the subprocess marker has the NEWER mtime,
|
|
291
|
-
# exactly the condition that made the single-SID helper pick the wrong
|
|
292
|
-
# SID and deny the orchestrator's Write (the P260 race). Candidate
|
|
293
|
-
# enumeration must surface BOTH so neither is left out of the marker set.
|
|
294
|
-
mark_announced "architect" "$orch_uuid"
|
|
295
|
-
sleep 1
|
|
296
|
-
mark_announced "jtbd" "$sub_uuid"
|
|
297
|
-
output=$(candidates)
|
|
298
|
-
[[ "$output" == *"$orch_uuid"* ]]
|
|
299
|
-
[[ "$output" == *"$sub_uuid"* ]]
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
@test "candidates: includes the runtime-sid value even when no announce marker carries it" {
|
|
303
|
-
rt_uuid="cccccccc-0000-1111-2222-runtimeonly0"
|
|
304
|
-
printf '%s' "$rt_uuid" > "$SANDBOX_TMP/itil-runtime-sid.current"
|
|
305
|
-
output=$(candidates)
|
|
306
|
-
[[ "$output" == *"$rt_uuid"* ]]
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
@test "candidates: deduplicates a SID present in both the runtime-sid marker and an announce marker" {
|
|
310
|
-
dup_uuid="dddddddd-0000-1111-2222-duplicated00"
|
|
311
|
-
printf '%s' "$dup_uuid" > "$SANDBOX_TMP/itil-runtime-sid.current"
|
|
312
|
-
mark_announced "architect" "$dup_uuid"
|
|
313
|
-
output=$(candidates)
|
|
314
|
-
# The SID must appear exactly once — not once per source.
|
|
315
|
-
count=$(printf '%s\n' "$output" | grep -c "$dup_uuid")
|
|
316
|
-
[ "$count" -eq 1 ]
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
@test "candidates: announce-marker enumeration excludes markers older than the mtime window" {
|
|
320
|
-
rt_uuid="eeeeeeee-0000-1111-2222-runtimepick0"
|
|
321
|
-
fresh_uuid="ffffffff-0000-1111-2222-freshmarker0"
|
|
322
|
-
stale_uuid="99999999-0000-1111-2222-stalemarker0"
|
|
323
|
-
# runtime-sid wins get_current_session_id (so its single pick is the
|
|
324
|
-
# runtime value, not an announce marker) — this isolates the window
|
|
325
|
-
# behaviour to the announce-marker ENUMERATION step.
|
|
326
|
-
printf '%s' "$rt_uuid" > "$SANDBOX_TMP/itil-runtime-sid.current"
|
|
327
|
-
mark_announced "jtbd" "$fresh_uuid"
|
|
328
|
-
mark_announced "voice-tone" "$stale_uuid"
|
|
329
|
-
# Backdate the stale marker well beyond the window. touch -t is POSIX
|
|
330
|
-
# and portable across BSD (macOS) and GNU find/touch.
|
|
331
|
-
touch -t 202001010000 "$SANDBOX_TMP/voice-tone-announced-${stale_uuid}"
|
|
332
|
-
output=$(SESSION_CANDIDATE_WINDOW_MINS=60 bash -c "source '$HELPER'; get_candidate_session_ids")
|
|
333
|
-
[[ "$output" == *"$rt_uuid"* ]]
|
|
334
|
-
[[ "$output" == *"$fresh_uuid"* ]]
|
|
335
|
-
[[ "$output" != *"$stale_uuid"* ]]
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
@test "candidates: empty output when no markers, no runtime-sid, and no env var" {
|
|
339
|
-
output=$(candidates)
|
|
340
|
-
[ -z "$output" ]
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
@test "candidates: env-var SID is included as a candidate" {
|
|
344
|
-
env_uuid="12121212-0000-1111-2222-envvarsid000"
|
|
345
|
-
other_uuid="34343434-0000-1111-2222-announced000"
|
|
346
|
-
mark_announced "architect" "$other_uuid"
|
|
347
|
-
output=$(CLAUDE_SESSION_ID="$env_uuid" SESSION_MARKER_DIR="$SANDBOX_TMP" bash -c "source '$HELPER'; get_candidate_session_ids")
|
|
348
|
-
[[ "$output" == *"$env_uuid"* ]]
|
|
349
|
-
# The concurrent announce marker is still enumerated alongside the env SID.
|
|
350
|
-
[[ "$output" == *"$other_uuid"* ]]
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
# P380: on macOS /tmp is a symlink to /private/tmp. The `find <symlink>
|
|
354
|
-
# -maxdepth 1` enumeration in get_candidate_session_ids in default (-P) mode
|
|
355
|
-
# refuses to descend the start-point symlink, so it silently adds zero
|
|
356
|
-
# concurrent SIDs; the `-L` flag follows it. (get_current_session_id uses a
|
|
357
|
-
# shell glob (`ls`), which traverses the symlink regardless — so this test
|
|
358
|
-
# needs a SECOND concurrent marker whose SID is reachable ONLY via the find
|
|
359
|
-
# enumeration to isolate the bug.) RED without `-L`, GREEN with it.
|
|
360
|
-
@test "candidates: enumerates concurrent markers when SESSION_MARKER_DIR is a symlink (P380 macOS /tmp)" {
|
|
361
|
-
primary_uuid="aabbccdd-0000-1111-2222-symlinkprim0" # picked by get_current_session_id glob
|
|
362
|
-
concurrent_uuid="eeff0011-0000-1111-2222-symlinkconc0" # reachable only via the find enum
|
|
363
|
-
mark_announced "architect" "$primary_uuid"
|
|
364
|
-
mark_announced "jtbd" "$concurrent_uuid"
|
|
365
|
-
link_tmp="${SANDBOX_TMP}.link"
|
|
366
|
-
ln -s "$SANDBOX_TMP" "$link_tmp"
|
|
367
|
-
output=$(SESSION_MARKER_DIR="$link_tmp" bash -c "source '$HELPER'; get_candidate_session_ids")
|
|
368
|
-
rm -f "$link_tmp"
|
|
369
|
-
[[ "$output" == *"$concurrent_uuid"* ]]
|
|
370
|
-
}
|