Skip to content

feat(pr-check): guard task PRs against Squad internal artifacts - #257

Merged
90sRehem merged 7 commits into
mainfrom
sq/squad-internal-artifact-commit-guard
Oct 9, 2026
Merged

90sRehem merged 7 commits into
mainfrom
sq/squad-internal-artifact-commit-guard

Conversation

@90sRehem

@90sRehem 90sRehem commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Intent

Make Squad's internal execution checklists impossible to accidentally ship in a project commit, preventing recurrence of the T04 and T10 incidents where checklists were removed from delivered PRs after an ambiguous data//artifacts/ path was resolved against the project repository. The plan-execute@1 brief must state that the checklist lives only in the Squad base's data//artifacts/ and that no project commit may contain a Squad internal artifact. Generated briefs must carry that explicit path and prohibition. When sq-pr-check records a task PR, inspect changed file paths and prominently name any file under that task's data// path; inability to fetch GitHub files remains non-fatal, while GitLab file-list fetch or parsing failures must be reported loudly rather than skipped. For GitLab, use the supported, version-verified glab default-JSON paginated diff response parsed with python3 JSONDecoder.raw_decode, never unsupported --jq or --output flags. Add behavior tests for generated guidance, synthetic task-specific artifact paths, GitLab pagination/fetch failure, GitHub wiring, and a glab test stub that rejects unknown flags. Preserve existing brief, playbook, and PR-check behavior; leave project CONTRIBUTING/CI enforcement and retroactive cleanup of merged commits out of scope, and do not change plan-execute@1's required plan fields or any other playbook. Keep scripts shellcheck-clean and follow Squad's one-sentence-per-line and no-agent-co-author conventions.

What Changed

  • Added bin/sq-pr-artifact-guard.sh, which reports changed paths under data/<task-id>/, and wired sq-pr-check.sh to run it against the PR file list so task-owned Squad artifacts are named before arming; GitHub fetch failures stay non-fatal while GitLab fetch or parse failures are reported loudly.
  • GitLab inspection uses glab api .../merge_requests/<n>/diffs with --paginate and an explicit --hostname, parsed with python3 JSONDecoder.raw_decode, avoiding unsupported --jq/--output flags.
  • Updated the plan-execute@1 playbook to state the checklist lives only in the Squad base's data/<id>/artifacts/ and that no project commit may contain a Squad internal artifact, and added behavior tests plus docs for generated guidance, task-specific paths, GitLab pagination/fetch/parse handling, GitHub wiring, and a glab stub that rejects unknown flags.

Risk Assessment

✅ Low: The change is a bounded, advisory artifact-path guard plus guidance text and behavior tests; the only fix-round code change (--hostname) is a version-supported glab flag and is directly asserted by its test, so no material source risk remains.

Testing

Ran the three directly affected test files (new sq-pr-artifact-guard test, sq-brief, and the full sq-pr-check-security suite) and all passed; the official runner executes the new test in family=pr-forge and the coverage guard accepts it. I then exercised the real end-user surfaces: generated the plan-execute@1 brief and confirmed it states the checklist lives only in the Squad base's data/<id>/artifacts/ and that no project commit may carry a Squad internal artifact; drove the real bin/sq-pr-check.sh against a flag-rejecting stub glab/gh to show a paginated default-JSON GitLab diff response names task-owned paths (including a later page) and not other tasks, a GitLab fetch or parse failure is warned loudly while arming still succeeds, a clean list is silent, and the GitHub PR file list names the task-owned artifact while a GitHub fetch failure stays non-fatal. The real glab binary is not installed in this sandbox, so the GitLab CLI contract was validated against the documented, version-pinned flag set (glab 1.53.0: --paginate, --hostname, default json; no --jq) and the required flag-rejecting stub rather than the live CLI. Worktree left clean; unrelated 'cost report could not be published' lines in the transcripts come from the sandbox lacking the sq-gh shim and are not part of this change.

Evidence: Generated plan-execute@1 brief with the Squad-base checklist location and commit prohibition

3. Follow tlc-implement for the implementation method and write the checklist only under the Squad base's data/&lt;id&gt;/artifacts/, never in the project repository. A project commit must never carry a Squad internal artifact. ... - A checklist under the Squad base's data/&lt;id&gt;/artifacts/ recording the plan fields as realized... - No project commit contains this Squad internal artifact.

You are an operator: an autonomous worker agent managed by Squad. Work on your own; do not wait for a human.

# Task
{TASK}

# Herdr lifecycle declaration - NOT ENABLED
**HARD SAFETY GATE:** this scaffold cannot inspect the task text that replaces `{TASK}` later.
If the task will start, stop, delete, restart, profile, or otherwise drive Herdr lifecycle behavior, stop and regenerate the brief with `--herdr-lab` before dispatch.
Do not add Herdr lifecycle commands to this unguarded brief by hand.

Execution playbook: id=plan-execute version=1

# Execution playbook: `plan-execute@1`

## Accepted brief form

This contract accepts `strike` only, and it applies when the resolved worker lane is an execution-class model: a lower-context lane that carries out a plan instead of designing one.
The brief must carry a complete `## Execution plan` section, materialized from the planning artifact - the TLC `data/<plan>/tasks/T0X.md` contract when one exists - and `bin/sq-plan-validate.sh` structurally validates it before dispatch.
The planner owns the design; the executing worker receives the plan, not the sources it was derived from.

## Required sequence

1. Accept a brief whose `## Execution plan` section is complete. The section must carry these five labels, each written as a label line followed by its list entries: `Files to touch` (at least one path-like entry, i.e. real file paths), `Ordered steps` (steps numbered, e.g. `1.`), `Acceptance criteria`, `Verification commands` (a command to run), and `Out of scope` (an explicit out-of-scope statement).
2. Execute the plan as written; do not redesign, re-scope, or re-plan the change.
3. Follow `tlc-implement` for the implementation method and write the checklist only under the Squad base's `data/<id>/artifacts/`, never in the project repository.
   A project commit must never carry a Squad internal artifact.
4. Stop with `blocked:` when the plan is incomplete, internally contradictory, or contradicted by the code, naming the exact gap rather than filling it by inference.
5. Report acceptance evidence and the verification command result; leave review, fixes, delivery, and merge to the existing owners.

## Required evidence

- The brief's materialized `## Execution plan` section, with every required field present and non-empty.
- A checklist under the Squad base's `data/<id>/artifacts/` recording the plan fields as realized: files touched as planned, steps executed, acceptance met, and the verification command run.
- No project commit contains this Squad internal artifact.
- A `blocked:` report naming the missing or contradictory plan element when execution cannot proceed safely.

## Exit predicate

Every planned file and step is realized or explicitly reported, acceptance criteria are met, and the planned verification command has been run with its result recorded.

## Stop conditions

Stop when the plan is incomplete or internally contradictory, when a step contradicts the current code, when an unplanned design decision would be required, or when the change would materially exceed the materialized plan.

## Ownership and anti-patterns

The planner (with the commander) owns design, and `AGENTS.md` section 7 plus `execution-playbooks` own selection; this contract only carries the materialized plan into execution.
Do not redesign the change, silently widen scope, edit or defer the plan instead of executing it, or create playbook-owned review, delivery, approval, or terminal authority.

# Setup
You are in a disposable git worktree of repo, at a detached HEAD on a clean default branch.

**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`; both must resolve to the disposable task worktree you were launched in, such as a fob pool path or an Orca-managed worktree, not the primary checkout Squad operates from.
The path check is authoritative: `git rev-parse --git-dir` and `git rev-parse --git-common-dir` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append `blocked: launched in primary checkout, not an isolated worktree` to the status file and stop.

1. First action: create your branch: `git checkout -b sq/lifecycle-plan-execute`
2. Run `drill doctor`; if it reports the repo is not initialized here, run `drill init`.

# Rules
1. Never push to the default branch. Never merge a PR. Never push your branch or open a PR yourself, through sq-gh or any other route - drill is the only path from a committed branch to a PR in this mode.
2. Stay inside this worktree; modify nothing outside it.
3. Use sq-gh for GitHub operations and sq-browser for browser operations; for frontend validation use the Playwright tooling in docs/playwright-validation.md.
4. Report status by appending one line:
   `echo "{state}: {one short line}" >> '/tmp/drill-evidence/01M4H447WZKR8KNX0RKNKKQX6Z/sq-brief-work/state/lifecycle-plan-execute.status'`
   States: working, needs-decision, blocked, paused, done, failed.
   Each append wakes Squad, so report sparingly: only phase changes a supervisor
   would act on (setup done, bug reproduced, fix implemented, validation passed) and the
   needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines;
   Squad reads your pane for that.
   A mid-task `working:` line (including setup complete) is nonterminal: do not end the
   turn after it; continue the same stage until a defined `done:` gate under Definition of done.
   Use `paused: {why}` - distinct from `blocked:` - ONLY when you are deliberately idling on a
   known external wait you expect to clear on its own (an upstream release, a rate-limit reset,
   a scheduled window): Squad then leaves your idle pane alone and rechecks it on a long
   cadence instead of treating it as a possible wedge. Use `blocked:` when you are stuck and need help.
5. If you hit the same obstacle twice, append `blocked: {why}` and stop; Squad will help.
6. If a decision belongs above the implementation worker (product choices, destructive actions, ask-user findings),
   append `needs-decision [key=<slug>]: {summary of options}` and stop. Squad will apply the configured authority and reply with the decision.
   Put the same `[key=<slug>]` immediately after the verb on `resolved` lines; without a key, both lines use `default`.
   A decision or blocker you opened stays open until a `resolved` line carrying its exact key lands; a later `done:` or `working:` line never closes it, even when the answer is what started that work.
   Squad's reply normally writes that closing line at answer time; when a blocker or wait clears WITHOUT a Squad reply, append `resolved: {how it cleared}` yourself (same `[key=<slug>]` if you opened it with one) as you resume.
7. Never stop, restart, or update the shared `drill` daemon - it is one instance serving
   every lane/base, so restarting it kills other lanes' in-flight pipeline runs. On ANY drill
   daemon error, append `blocked: {the daemon error}` and stop; only Squad manages the daemon.

# Project memory
If `AGENTS.md` or `CLAUDE.md` already exists, or if this task produced durable project-intrinsic knowledge, run `/home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z/bin/sq-ensure-agents-md.sh .` in the worktree.
Record only project knowledge useful to almost every future session.
For anything the codebase already shows, prefer a pointer to the authoritative file, command, or doc over copying the detail.
If you touch a project `AGENTS.md` that lacks `## Maintaining this file`, add that short self-governance section from `/home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z/bin/sq-ensure-agents-md.sh` in the same pass.
Keep it proportionate: skip `AGENTS.md` edits for trivial tasks that produced no durable project knowledge.

# Definition of done
Delivery contract: mode=drill

## Phase 1: implement and commit
Implement the change and commit it on your branch.
A commit is NOT completion - it is the input to the validation pipeline.

## Phase 2: run the pipeline
After committing, invoke `/drill` immediately.
The pipeline owns review, fixes, tests, docs, push, PR, and CI; you respond to its gates.
Follow the guidance drill itself provides for the mechanics: it loads when you invoke /drill, and `drill axi run --help` plus the `help` lines in each `axi` response are authoritative and version-matched to the installed binary.
When starting drill, make `--intent` preserve all relevant content from this brief's `# Task` section plus every later accepted Squad requirement, clarification, constraint, exclusion, and supersession, carrying only each requirement's current accepted form; retain direct requirements instead of substituting a diff summary, and exclude generic operational, status, delivery, and other scaffold boilerplate unless it is task-specific.
Do not hand-edit, commit, or fix findings yourself while a run is active - the pipeline applies every fix.

Two Squad-specific rules layer on top of that guidance:
- ask-user findings are never yours to answer: escalate to Squad (rule 6) and stop.
  Squad applies the authority contract in its `AGENTS.md` and obtains any required commander decision.
  When the decision comes back, feed it to the gate with `drill axi respond` and let the pipeline apply it - do not route the question to "the user" or implement the fix yourself.
- Avoid `--yes`: it would silently bypass Squad's authority check and any required commander escalation.

## Phase 3: self-check before done
Before appending `done:`, verify ALL of the following - if any is false, the task is not done:
- A PR exists for your branch (the pipeline opened it).
- CI checks on that PR are green (`/drill` reported CI green).
If the PR is not open or CI is not green, keep working through the pipeline gates instead of appending `done:`.

## done signal
After /drill reports CI green (the CI-ready return point - do not wait for it to keep monitoring in the background until merge), append `done: PR {url} checks green` to the status file and stop. You are finished.
Evidence: End-to-end sq-pr-check GitLab guard: paginated naming, fetch failure, clean silence

### SCENARIO 1: paginated default JSON with a task-owned artifact warning: PR contains Squad internal artifact path(s) for task task-a; remove them before merge: data/task-a/artifacts/checklist.md data/task-a/artifacts/page-2.md armed: state/task-a.check.sh exit=0 --- glab invocations --- GLAB CALL: api projects/group%2Fsubgroup%2Fproject/merge_requests/7/diffs --paginate --hostname gitlab.example ### SCENARIO 2: GitLab fetch failure must be reported loudly (non-fatal) warning: GitLab merge request https://gitlab.example/group/subgroup/project/-/merge_requests/7 was not inspected for Squad internal artifacts because its changed-file list could not be fetched. armed: state/task-a.check.sh exit=0 ### SCENARIO 3: clean file list must be silent armed: state/task-a.check.sh exit=0

### SCENARIO 1: paginated default JSON with a task-owned artifact
warning: PR contains Squad internal artifact path(s) for task task-a; remove them before merge:
data/task-a/artifacts/checklist.md
data/task-a/artifacts/page-2.md
warning: cost report could not be published: /home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z/bin/sq-cost.sh: line 278: sq-gh: command not found
armed: state/task-a.check.sh
exit=0
--- glab invocations ---
GLAB CALL: api projects/group%2Fsubgroup%2Fproject/merge_requests/7/diffs --paginate --hostname gitlab.example

### SCENARIO 2: GitLab fetch failure must be reported loudly (non-fatal)
warning: GitLab merge request https://gitlab.example/group/subgroup/project/-/merge_requests/7 was not inspected for Squad internal artifacts because its changed-file list could not be fetched.
warning: cost report could not be published: /home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z/bin/sq-cost.sh: line 278: sq-gh: command not found
armed: state/task-a.check.sh
exit=0

### SCENARIO 3: clean file list must be silent
warning: cost report could not be published: /home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z/bin/sq-cost.sh: line 278: sq-gh: command not found
armed: state/task-a.check.sh
exit=0
Evidence: End-to-end sq-pr-check GitLab parse failure plus GitHub naming and non-fatal fetch failure

### SCENARIO 4: malformed GitLab JSON must be reported loudly (non-fatal) json.decoder.JSONDecodeError: Expecting value: line 1 column 39 (char 38) warning: GitLab merge request https://gitlab.example/group/project/-/merge_requests/7 was not inspected for Squad internal artifacts because its changed-file response could not be parsed. armed: state/task-a.check.sh exit=0 ### SCENARIO 5: GitHub PR file list names the task-owned artifact warning: PR contains Squad internal artifact path(s) for task task-a; remove them before merge: data/task-a/artifacts/checklist.md armed: state/task-a.check.sh exit=0 --- gh invocations --- GH CALL: pr view https://github.com/o/r/pull/7 --json headRefOid -q .headRefOid GH CALL: api repos/o/r/pulls/7/files --paginate --jq .[].filename ### SCENARIO 6: GitHub fetch failure stays non-fatal armed: state/task-a.check.sh exit=0

### SCENARIO 4: malformed GitLab JSON must be reported loudly (non-fatal)
Traceback (most recent call last):
  File "<string>", line 10, in <module>
    page, offset = decoder.raw_decode(text, offset)
                   ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/json/decoder.py", line 363, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 39 (char 38)
warning: GitLab merge request https://gitlab.example/group/project/-/merge_requests/7 was not inspected for Squad internal artifacts because its changed-file response could not be parsed.
warning: cost report could not be published: /home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z/bin/sq-cost.sh: line 278: sq-gh: command not found
armed: state/task-a.check.sh
exit=0

### SCENARIO 5: GitHub PR file list names the task-owned artifact
warning: PR contains Squad internal artifact path(s) for task task-a; remove them before merge:
data/task-a/artifacts/checklist.md
warning: cost report could not be published: /home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z/bin/sq-cost.sh: line 278: sq-gh: command not found
armed: state/task-a.check.sh
exit=0
--- gh invocations ---
GH CALL: pr view https://github.com/o/r/pull/7 --json headRefOid -q .headRefOid
GH CALL: api repos/o/r/pulls/7/files --paginate --jq .[].filename

### SCENARIO 6: GitHub fetch failure stays non-fatal
warning: cost report could not be published: /home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z/bin/sq-cost.sh: line 278: sq-gh: command not found
armed: state/task-a.check.sh
exit=0
Evidence: Reproducible demo scripts for the two end-to-end transcripts
#!/usr/bin/env bash
# End-to-end demo: real bin/sq-pr-check.sh + stub glab that rejects unknown flags.
set -u
ROOT="/home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z"
EVID="/tmp/drill-evidence/01M4H447WZKR8KNX0RKNKKQX6Z"
DIR="$EVID/demo-case"
rm -rf "$DIR"
mkdir -p "$DIR/home/state" "$DIR/home/data" "$DIR/home/config" "$DIR/wt" "$DIR/fakebin" "$DIR/root/bin"
cat > "$DIR/root/bin/sq-guard.sh" <<'SH'
#!/usr/bin/env bash
exit 0
SH
chmod +x "$DIR/root/bin/sq-guard.sh"

# Stub glab: accepts only the documented flags; rejects anything else with exit 2.
cat > "$DIR/fakebin/glab" <<'SH'
#!/usr/bin/env bash
printf 'GLAB CALL: %s\n' "$*" >> "$SQUAD_DEMO_LOG"
for arg in "$@"; do
  case "$arg" in
    --paginate|-R|--hostname) ;;
    -*) echo "unknown flag: $arg" >&2; exit 2 ;;
  esac
done
[ "${DEMO_GLAB_FAIL:-0}" = 0 ] || exit 1
case " $* " in
  *" api "*) cat "$DEMO_GLAB_JSON" ;;
  *) printf 'title:\tfixture\nstate:\topened\n' ;;
esac
SH
chmod +x "$DIR/fakebin/glab"

cat > "$DIR/home/state/task-a.meta" <<META
window=Squad:sq-task-a
endpoint_task_id=task-a
worktree=$DIR/wt
project=$DIR/project
kind=strike
mode=drill
META

# Paginated default-JSON diffs (two pages as ndjson), including a task-owned path.
cat > "$DIR/diffs.json" <<'JSON'
[{"old_path":"src/main.sh","new_path":"src/main.sh"},{"old_path":"data/task-a/artifacts/checklist.md","new_path":"data/task-a/artifacts/checklist.md"}]
[{"old_path":"data/task-a/artifacts/page-2.md","new_path":"data/task-a/artifacts/page-2.md"},{"old_path":"data/other-task/report.md","new_path":"data/other-task/report.md"}]
JSON

URL="https://gitlab.example/group/subgroup/project/-/merge_requests/7"
run() {
  SQUAD_ROOT_OVERRIDE="$DIR/root" SQUAD_BASE="$DIR/home" \
    SQUAD_DEMO_LOG="$DIR/glab.log" DEMO_GLAB_JSON="$DIR/diffs.json" \
    PATH="$DIR/fakebin:/usr/bin:/bin:/usr/sbin:/sbin" \
    "$ROOT/bin/sq-pr-check.sh" task-a "$URL" 2>&1
}

echo "### SCENARIO 1: paginated default JSON with a task-owned artifact"
: > "$DIR/glab.log"
run; echo "exit=$?"
echo "--- glab invocations ---"; cat "$DIR/glab.log"

echo
echo "### SCENARIO 2: GitLab fetch failure must be reported loudly (non-fatal)"
: > "$DIR/glab.log"
DEMO_GLAB_FAIL=1 run; echo "exit=$?"

echo
echo "### SCENARIO 3: clean file list must be silent"
: > "$DIR/glab.log"
printf '%s\n' '[{"old_path":"src/main.sh","new_path":"src/main.sh"}]' > "$DIR/diffs.json"
run; echo "exit=$?"
Evidence: Reproducible demo script for parse-failure and GitHub scenarios
#!/usr/bin/env bash
# End-to-end demo: malformed GitLab JSON parse failure and GitHub wiring.
set -u
ROOT="/home/rehem/.drill/worktrees/f8f4d2080c84/01M4H447WZKR8KNX0RKNKKQX6Z"
EVID="/tmp/drill-evidence/01M4H447WZKR8KNX0RKNKKQX6Z"
DIR="$EVID/demo-parse-case"
rm -rf "$DIR"
mkdir -p "$DIR/home/state" "$DIR/home/data" "$DIR/home/config" "$DIR/wt" "$DIR/fakebin" "$DIR/root/bin"
cat > "$DIR/root/bin/sq-guard.sh" <<'SH'
#!/usr/bin/env bash
exit 0
SH
chmod +x "$DIR/root/bin/sq-guard.sh"

cat > "$DIR/fakebin/glab" <<'SH'
#!/usr/bin/env bash
printf 'GLAB CALL: %s\n' "$*" >> "$SQUAD_DEMO_LOG"
for arg in "$@"; do
  case "$arg" in
    --paginate|-R|--hostname) ;;
    -*) echo "unknown flag: $arg" >&2; exit 2 ;;
  esac
done
cat "$DEMO_GLAB_JSON"
SH
chmod +x "$DIR/fakebin/glab"

cat > "$DIR/fakebin/gh" <<'SH'
#!/usr/bin/env bash
printf 'GH CALL: %s\n' "$*" >> "$SQUAD_GH_LOG"
case " $* " in
  *"/pulls/"*"files"*)
    [ "${DEMO_GH_FAIL:-0}" = 0 ] || exit 1
    printf '%s\n' "${DEMO_GH_FILES:-}"
    ;;
  *" headRefOid "*) printf '%s\n' '0123456789abcdef0123456789abcdef01234567' ;;
esac
SH
chmod +x "$DIR/fakebin/gh"

cat > "$DIR/home/state/task-a.meta" <<META
window=Squad:sq-task-a
endpoint_task_id=task-a
worktree=$DIR/wt
project=$DIR/project
kind=strike
mode=drill
META

URL_GL="https://gitlab.example/group/project/-/merge_requests/7"
URL_GH="https://github.com/o/r/pull/7"

echo "### SCENARIO 4: malformed GitLab JSON must be reported loudly (non-fatal)"
printf '%s\n' '[{"old_path":"src/main.sh","new_path":' > "$DIR/diffs.json"
: > "$DIR/glab.log"
SQUAD_ROOT_OVERRIDE="$DIR/root" SQUAD_BASE="$DIR/home" \
  SQUAD_DEMO_LOG="$DIR/glab.log" DEMO_GLAB_JSON="$DIR/diffs.json" \
  PATH="$DIR/fakebin:/usr/bin:/bin:/usr/sbin:/sbin" \
  "$ROOT/bin/sq-pr-check.sh" task-a "$URL_GL" 2>&1; echo "exit=$?"

echo
echo "### SCENARIO 5: GitHub PR file list names the task-owned artifact"
: > "$DIR/gh.log"
SQUAD_ROOT_OVERRIDE="$DIR/root" SQUAD_BASE="$DIR/home" \
  SQUAD_GH_LOG="$DIR/gh.log" \
  DEMO_GH_FILES=$(printf '%s\n' 'src/main.sh' 'data/task-a/artifacts/checklist.md' 'data/other-task/report.md') \
  PATH="$DIR/fakebin:/usr/bin:/bin:/usr/sbin:/sbin" \
  "$ROOT/bin/sq-pr-check.sh" task-a "$URL_GH" 2>&1; echo "exit=$?"
echo "--- gh invocations ---"; cat "$DIR/gh.log"

echo
echo "### SCENARIO 6: GitHub fetch failure stays non-fatal"
: > "$DIR/gh.log"
DEMO_GH_FAIL=1 SQUAD_ROOT_OVERRIDE="$DIR/root" SQUAD_BASE="$DIR/home" \
  SQUAD_GH_LOG="$DIR/gh.log" \
  PATH="$DIR/fakebin:/usr/bin:/bin:/usr/sbin:/sbin" \
  "$ROOT/bin/sq-pr-check.sh" task-a "$URL_GH" 2>&1; echo "exit=$?"

Pipeline

Updates from git push drill

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 3 issues found → auto-fixed (2) ✅
  • ⚠️ tests/sq-pr-check-security.test.sh:2915 - The GitLab pagination fixture's second JSON page contains only an other-task path (data/other-task/report.md), which the guard filters out. Every assertion in test_gitlab_artifact_guard would still pass if the parser read only page 1, so the test does not actually prove that glab api --paginate output beyond the first page is parsed. Put a task-owned artifact (e.g. data/task-a/artifacts/page-2.md) on the second page and assert it is named, so the pass &#34;...parses paginated default JSON...&#34; claim is substantiated.
  • ℹ️ bin/sq-pr-check.sh:108 - The loud parse-failure branch for GitLab was added here, but no test exercises it: the GitLab fixture feeds valid JSON (two pages), an empty file, and a fetch failure. The intent requires that parsing failures be reported loudly rather than skipped, so add a malformed-JSON fixture and assert the could not be parsed warning (while arming still succeeds).
  • ℹ️ bin/sq-pr-check.sh:114 - The new artifact inspection is advisory: it prints a stderr warning and then still arms the merge poll (|| true), so a data/&lt;task-id&gt;/... file committed to the PR remains shippable if the warning is missed. That keeps the T04/T10 failure technically reachable, but the intent explicitly scopes out CONTRIBUTING/CI or commit-time enforcement and only requires the path to be named "prominently", so this is authorized containment and needs no action.

🔧 Fix: Substantiate GitLab pagination and malformed-JSON guard behavior tests
2 issues (1 warning, 1 info) still open:

  • ⚠️ bin/sq-pr-check.sh:88 - The GitLab artifact inspection runs glab api &#34;projects/$ENCODED_PROJECT/merge_requests/$NUMBER/diffs&#34; --paginate with no --hostname &#34;$HOST&#34;. In the pinned glab 1.53.0, glab api targets gitlab.com or the authenticated host of the current git directory unless --hostname (or GITLAB_HOST) overrides it; it does not derive the instance from the endpoint. Squad explicitly supports self-hosted GitLab and the poll resolves the instance from the recorded project URL via -R, so when glab's default host differs from the MR host this guard queries the wrong server. The common outcome is a 404 that only prints the loud was not inspected warning while arming still succeeds, but if the default host happens to serve a project at the same namespace/path and MR number, the wrong MR is inspected silently and the intended MR's artifacts are never named. Pass --hostname &#34;$HOST&#34;; the test stub currently rejects --hostname and must be updated to allow it and assert it is logged.
  • ℹ️ .agents/skills/execution-playbooks/references/plan-execute-v1.md:13 - This changed reference file has no entry in the changed-file map of bin/sq-test-run.sh. Running bin/sq-test-run.sh --list --changed --base 1757d143073dbdbf49fd704b9f239cc21bf7ed9c exits 2 with no changed-test mapping for source path: .agents/skills/execution-playbooks/references/plan-execute-v1.md, because families_for_changed_path only special-cases .agents/skills/*/SKILL.md and the literal-path test-reference scan finds no test containing this full path. The gap predates this change, but this required edit keeps tripping it. Consider adding a .agents/skills/execution-playbooks/references/* -&gt; pure-contract-unit case (which owns tests/sq-brief.test.sh) so the branch can be selected by the runner.

🔧 Fix: Pin GitLab artifact diff fetch to reviewed host
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash tests/sq-pr-artifact-guard.test.sh (guard names task-owned paths, ignores other tasks, silent when clean)
  • bash tests/sq-brief.test.sh (generated plan-execute brief carries the Squad-base path and commit prohibition)
  • bash tests/sq-pr-check-security.test.sh (full PR-check security suite, including new GitLab and GitHub artifact-guard cases)
  • bash tests/sq-pr-check-security.test.sh | grep -iE &#39;artifact guard&#39; (confirmed both new guard cases pass)
  • bin/sq-test-run.sh tests/sq-pr-artifact-guard.test.sh (runner executes the new test in family=pr-forge)
  • bin/sq-test-run.sh --list --family pr-forge | grep artifact-guard
  • bin/sq-test-run.sh --check-coverage (coverage guard accepts the new test)
  • SQUAD_BASE=&lt;tmp&gt; bin/sq-brief.sh lifecycle-plan-execute repo --mode drill --playbook plan-execute@1 (generated real brief inspected)
  • Manual E2E bin/sq-pr-check.sh task-a &lt;gitlab-url&gt; with a stub glab that rejects unknown flags: paginated default-JSON success, fetch failure, and clean-list silence
  • Manual E2E bin/sq-pr-check.sh task-a &lt;gitlab-url&gt; with malformed JSON and bin/sq-pr-check.sh task-a &lt;github-url&gt; with a stub gh: parse-failure warning, GitHub path naming, and non-fatal GitHub fetch failure
⚠️ **Document** - 1 info
  • ℹ️ docs/gitlab-merge-sentry.md:199 - The new GitLab branch of bin/sq-pr-check.sh calls glab api ... --paginate --hostname and parses default JSON with python3, but this maintainer-verification record still states glab JSON 'would need a JSON processor Squad does not require' and carries no version-verified evidence for the new invocation. The stub test pins the flag shape, yet glab is not installed in this environment to confirm the flags against the pinned 1.53.0 CLI. Left as a judgment call because the record's pr_head conclusion is unchanged and it is a dated empirical artifact.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@90sRehem

90sRehem commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Coding agent usage on this pull request

Contributor Agent Models Sessions Total tokens Cost
Squad task squad-internal-artifact-commit-guard Pi openai-codex/gpt-6-luna, unknown 1 32 million provider-recorded + estimate total: $15.92; provider-recorded: $0.11; estimate: $15.81

Token and model breakdown

Model Input Output Cache read Cache write Total tokens Cost
openai-codex/gpt-6-luna 402.6 thousand 24.5 thousand 5.7 million 0 6.1 million provider-recorded: $0.11
unknown 1.3 million 306.2 thousand 24.3 million 0 25.9 million estimate: $15.81

Source: Pi session JSONL usage records and Drill agent-invocation records, covering the task lifetime through report generation. Provider-recorded costs and list-price estimates are labeled separately; subscription usage is not represented as spend.

@90sRehem
90sRehem merged commit 1c74643 into main Oct 9, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant