muse-crew 0.14.1 → 0.14.3

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.
@@ -21,6 +21,17 @@
21
21
  # * old helper that does not know --check (usage to stderr, exit 1,
22
22
  # no CREDENTIAL markers) with DRY_RUN=1 -> no skip (fall-through
23
23
  # preserved: unknown is not absent)
24
+ # - detached fixtures (detached-HEAD audit REDO, 2026-09-19): a detached
25
+ # checkout with a local origin. Case 6 (origin/HEAD set, DRY_RUN=1) ->
26
+ # preflight passes through to PUBLISH_DRY_RUN=1 (no refusal — detached
27
+ # publication is legitimate). Case 7 (origin/HEAD deleted, DRY_RUN=1) ->
28
+ # PUBLISH_FAILED=env before any mutation, repo untouched (the genuinely
29
+ # unknowable destination fails in the preflight, ahead of the DRY_RUN
30
+ # block).
31
+ # - Case 8: attached checkout with no origin (DRY_RUN=1) -> PUBLISH_FAILED=env
32
+ # pre-mutation, repo untouched (2026-09-19 critic F-A1: the anchor used to
33
+ # check no-origin only on the detached branch, so attached+no-origin
34
+ # failed post-mutation after the version-bump commit and registry publish).
24
35
  # - --check probe unit test against the real helper: on the author
25
36
  # machine this expects exit 0 and exactly CREDENTIAL_OK=custom.npm.
26
37
  # NOTE: this case depends on the `custom.npm` credential existing in
@@ -52,6 +63,13 @@ git -C "$SCRATCH" config user.email "test@crew"
52
63
  git -C "$SCRATCH" config user.name "crew-test"
53
64
  echo x > "$SCRATCH/f.txt"
54
65
  git -C "$SCRATCH" add -A && git -C "$SCRATCH" commit -qm init
66
+ # The scratch repo needs an origin for the DRY_RUN pass-through cases
67
+ # (2026-09-19 critic F-A1: the push-destination anchor fails closed
68
+ # pre-mutation when origin is absent, attached or detached).
69
+ git init -q --bare "$TMPBASE/origin.git"
70
+ git -C "$SCRATCH" remote add origin "$TMPBASE/origin.git"
71
+ git -C "$SCRATCH" push -q origin main
72
+ git -C "$SCRATCH" remote set-head origin -a
55
73
 
56
74
  # --- Fake helpers ---
57
75
  cat > "$TMPBASE/helper-ok.py" <<'EOF'
@@ -78,6 +96,32 @@ print("Usage: helper-old.py <tarball-path>", file=sys.stderr)
78
96
  sys.exit(1)
79
97
  EOF
80
98
 
99
+ # --- Detached fixtures (detached-HEAD audit REDO, 2026-09-19): a detached
100
+ # checkout with a local origin. Case 6 runs with origin/HEAD set; Case 7
101
+ # deletes origin/HEAD first, exercising push-destination's genuinely-
102
+ # unknowable branch. ---
103
+ ORIGIN="$TMPBASE/origin-det.git"
104
+ git init -q --bare "$ORIGIN"
105
+ SCRATCH_DET="$TMPBASE/repo-detached"
106
+ git init -q -b main "$SCRATCH_DET"
107
+ git -C "$SCRATCH_DET" config user.email "test@crew"
108
+ git -C "$SCRATCH_DET" config user.name "crew-test"
109
+ echo x > "$SCRATCH_DET/f.txt"
110
+ git -C "$SCRATCH_DET" add -A && git -C "$SCRATCH_DET" commit -qm init
111
+ git -C "$SCRATCH_DET" remote add origin "$ORIGIN"
112
+ git -C "$SCRATCH_DET" push -q origin main
113
+ git -C "$SCRATCH_DET" remote set-head origin -a
114
+ git -C "$SCRATCH_DET" checkout -q --detach HEAD
115
+
116
+ # --- Attached no-origin fixture (F-A1): an attached checkout with no origin
117
+ # fails closed in the anchor, attached or detached. ---
118
+ SCRATCH_NOORIGIN="$TMPBASE/repo-no-origin"
119
+ git init -q -b main "$SCRATCH_NOORIGIN"
120
+ git -C "$SCRATCH_NOORIGIN" config user.email "test@crew"
121
+ git -C "$SCRATCH_NOORIGIN" config user.name "crew-test"
122
+ echo x > "$SCRATCH_NOORIGIN/f.txt"
123
+ git -C "$SCRATCH_NOORIGIN" add -A && git -C "$SCRATCH_NOORIGIN" commit -qm init
124
+
81
125
  ACTIONABLE="npm publish not configured on this machine — see docs/guide.md#npm-releases; code changes are merged, nothing was versioned or published"
82
126
 
83
127
  # --- Structural: extract the shipped preflight block by anchor ---
@@ -123,13 +167,23 @@ fi
123
167
 
124
168
  # Runner: full publish-npm.sh against the scratch repo. The preflight exits
125
169
  # before any mutation, so the repo needs no package.json.
170
+ # LIFECYCLE + CREW_HOME are exported because the preflight now resolves the
171
+ # push destination up front (2026-09-19: the push-destination resolution moved
172
+ # from step 13 to the preflight — fail before mutation, never after the
173
+ # registry publish; 2026-09-19 detached-HEAD audit REDO: a detached checkout
174
+ # resolves to HEAD:<destination>, refused only when genuinely unknowable).
175
+ # The scratch repo sits on branch `main`, so integration-target
176
+ # resolves cleanly and the npm-credential assertions are unaffected.
126
177
  run_full() { # run_full <helper_path> [extra_env...]
127
178
  local helper="$1"; shift
128
179
  local run="$TMPBASE/run-$RANDOM"
129
180
  mkdir -p "$run"
181
+ local crew_home="$TMPBASE/crew-home"
182
+ mkdir -p "$crew_home"
130
183
  (
131
184
  export TASK_ID="preflight-test" REPO_PATH="$SCRATCH" PKG="muse-crew" TARGET_VERSION="9.9.9"
132
185
  export NPM_PUBLISH_PY="$helper"
186
+ export LIFECYCLE="$SCRIPT_DIR/worktree-lifecycle.sh" CREW_HOME="$crew_home" CREW_REPO="$SCRATCH"
133
187
  # A bare `export` (empty "$@") would dump the environment to stdout and
134
188
  # corrupt the captured run path — only export when extras are given.
135
189
  if [ $# -gt 0 ]; then export "$@"; fi
@@ -140,11 +194,11 @@ run_full() { # run_full <helper_path> [extra_env...]
140
194
  echo "$run"
141
195
  }
142
196
 
143
- repo_state() { # prints "HEAD=<sha> STATUS=<porcelain-lines> TGZ=<count>"
144
- local head status tgz
145
- head="$(git -C "$SCRATCH" rev-parse HEAD)"
146
- status="$(git -C "$SCRATCH" status --porcelain | wc -l)"
147
- tgz="$(find "$SCRATCH" -maxdepth 1 -name '*.tgz' | wc -l)"
197
+ repo_state() { # repo_state [repo]: "HEAD=<sha> STATUS=<porcelain-lines> TGZ=<count>"
198
+ local head status tgz repo="${1:-$SCRATCH}"
199
+ head="$(git -C "$repo" rev-parse HEAD)"
200
+ status="$(git -C "$repo" status --porcelain | wc -l)"
201
+ tgz="$(find "$repo" -maxdepth 1 -name '*.tgz' | wc -l)"
148
202
  echo "HEAD=$head STATUS=$status TGZ=$tgz"
149
203
  }
150
204
 
@@ -203,6 +257,41 @@ else
203
257
  no "real helper --check: code=$CHECK_CODE out='$CHECK_OUT' (expected CREDENTIAL_OK=custom.npm — this case is credential-dependent; see header)"
204
258
  fi
205
259
 
260
+ # --- Case 6: detached + resolvable destination + DRY_RUN=1 -> pass-through, no refusal ---
261
+ RUN="$(run_full "$TMPBASE/helper-ok.py" DRY_RUN=1 REPO_PATH="$SCRATCH_DET")"
262
+ CODE="$(cat "$RUN/code")"; OUT="$(cat "$RUN/out")"
263
+ if [ "$CODE" -eq 0 ] && [[ "$OUT" == *"PUBLISH_DRY_RUN=1"* ]] \
264
+ && [[ "$OUT" != *"PUBLISH_SKIPPED"* ]] && [[ "$OUT" != *"PUBLISH_FAILED"* ]]; then
265
+ ok "detached + resolvable destination: preflight passes through to PUBLISH_DRY_RUN=1 (no refusal)"
266
+ else
267
+ no "detached resolvable: code=$CODE out='$OUT'"
268
+ fi
269
+
270
+ # --- Case 7: detached + origin/HEAD unset -> PUBLISH_FAILED=env before any mutation ---
271
+ git -C "$SCRATCH_DET" remote set-head origin -d
272
+ BEFORE="$(repo_state "$SCRATCH_DET")"
273
+ RUN="$(run_full "$TMPBASE/helper-ok.py" DRY_RUN=1 REPO_PATH="$SCRATCH_DET")"
274
+ CODE="$(cat "$RUN/code")"; OUT="$(cat "$RUN/out")"; AFTER="$(repo_state "$SCRATCH_DET")"
275
+ if [ "$CODE" -ne 0 ] && [[ "$OUT" == *"PUBLISH_FAILED=env"* ]] \
276
+ && [[ "$OUT" != *"PUBLISH_SKIPPED"* ]] && [[ "$OUT" != *"PUBLISH_DRY_RUN=1"* ]] \
277
+ && [ "$BEFORE" = "$AFTER" ]; then
278
+ ok "detached + unknowable destination: fails closed pre-mutation, repo untouched"
279
+ else
280
+ no "detached unknowable: code=$CODE state=$BEFORE/$AFTER out='$OUT'"
281
+ fi
282
+
283
+ # --- Case 8: attached + no origin -> PUBLISH_FAILED=env before any mutation ---
284
+ BEFORE="$(repo_state "$SCRATCH_NOORIGIN")"
285
+ RUN="$(run_full "$TMPBASE/helper-ok.py" DRY_RUN=1 REPO_PATH="$SCRATCH_NOORIGIN")"
286
+ CODE="$(cat "$RUN/code")"; OUT="$(cat "$RUN/out")"; AFTER="$(repo_state "$SCRATCH_NOORIGIN")"
287
+ if [ "$CODE" -ne 0 ] && [[ "$OUT" == *"PUBLISH_FAILED=env"* ]] \
288
+ && [[ "$OUT" != *"PUBLISH_SKIPPED"* ]] && [[ "$OUT" != *"PUBLISH_DRY_RUN=1"* ]] \
289
+ && [ "$BEFORE" = "$AFTER" ]; then
290
+ ok "attached + no origin: fails closed pre-mutation, repo untouched"
291
+ else
292
+ no "attached no-origin: code=$CODE state=$BEFORE/$AFTER out='$OUT'"
293
+ fi
294
+
206
295
  bash -n "$PUBLISH" && ok "publish-npm.sh syntax OK" || no "publish-npm.sh syntax"
207
296
 
208
297
  echo ""
@@ -298,8 +298,9 @@ if (head !== commit) {
298
298
  // 2026-09-18, blocker 4: bind by exact (task_id, commit, attempt) when the
299
299
  // attempt is known — the original and retry attempts share a commit, and
300
300
  // positional selection is luck. Within one attempt, select the OLDEST
301
- // submitted entry (the trigger issuance; per blocker 2, audit observations
302
- // are now "build-observed", not "submitted").
301
+ // submitted entry (the trigger issuance; a receipt-bearing second "submitted"
302
+ // may follow it — the oldest binds the trigger instant, the latest carries
303
+ // the observed agent_id).
303
304
  let triggerTs = null;
304
305
  let manifestBefore = null;
305
306
  try {
@@ -313,8 +314,6 @@ try {
313
314
  (attempt == null || e.attempt === attempt)
314
315
  );
315
316
  if (candidates.length > 0) {
316
- // Oldest first: the trigger issuance is the first submitted entry for
317
- // the attempt (observations are "build-observed" per blocker 2).
318
317
  candidates.sort((a, b) => (a.ts || "").localeCompare(b.ts || ""));
319
318
  const e = candidates[0];
320
319
  triggerTs = e.ts || null;