Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ef4a84a
test(acq): add offline provider adoption verification
mogul Oct 22, 2026
7ea0091
test(verify): prepare provider adoption validation
mogul Oct 22, 2026
9e59d9a
fix(msb): canonicalize host-config dir before --volume mount
mogul Oct 24, 2026
afabe26
fix(sbx): mount provider generation after agent
mogul Oct 24, 2026
636bc2f
test(sbx): assert provider root token position
mogul Oct 24, 2026
55dd201
fix(verify): consolidate live backend corrections
mogul Oct 24, 2026
0bdf2a7
test(verify): preserve live provider adoption evidence
mogul Oct 24, 2026
a9032d9
docs: record successful provider adoption matrix
mogul Oct 24, 2026
767f30b
test(msb): supply canonicalizer in adapter signal seam
mogul Oct 24, 2026
ed9b1ac
test(msb): retain projection seam in canonical mount test
mogul Oct 25, 2026
97270b5
fix(sbx): place provider mount after workspace
mogul Oct 25, 2026
1e5fca5
test(sbx): expect provider mount after workspace
mogul Oct 25, 2026
b15f4f6
test(backends): refresh create argv expectations
mogul Oct 25, 2026
e47e1e0
docs: record final provider-neutral backend matrix
mogul Oct 25, 2026
91f3d1a
docs: record literal MSB OpenCode credential flow
mogul Oct 25, 2026
47ac658
docs: record literal SBX OpenCode credential flow
mogul Oct 25, 2026
fb912d7
fix(provider-input): report malformed manifest errors
mogul Oct 25, 2026
6a9186a
docs: record malformed provider validation matrix
mogul Oct 25, 2026
580fe80
feat(providers): support explicit provider kit selection
mogul Oct 25, 2026
1b883f9
fix(providers): support empty selection under nounset
mogul Oct 25, 2026
1d243dc
docs: record authoritative empty provider matrix
mogul Oct 25, 2026
522c699
test(acq): refresh provider selection CI expectations
mogul Oct 25, 2026
f0bb5d6
test(msb): isolate external signal fixture state
mogul Oct 25, 2026
d9499e5
fix(sbx): move extra kit state to host
mogul Oct 25, 2026
384c3f6
test(verify): bind provider model check to the projected snapshot ide…
mogul Oct 26, 2026
dfb0aa2
docs(verify): use --no-provider-kits for the removal procedure; add p…
mogul Oct 26, 2026
479dfb3
fix(verify): avoid heredoc-in-command-substitution; tighten removal p…
mogul Oct 26, 2026
61631d9
fix(verify): fail the identity check when `opencode models` exits non…
mogul Oct 26, 2026
b8bb051
docs(verify): add an explicit merge gate; mark removal assertions pen…
mogul Oct 26, 2026
d9f2ce5
docs(verify): tighten merge gate wording per review
mogul Oct 26, 2026
ec8d5e1
Merge remote-tracking branch 'origin/main' into feat/601-provider-neu…
mogul Oct 26, 2026
761f706
docs(verify): record live validation results at ec8d5e17 and update t…
mogul Oct 27, 2026
8a59493
Merge remote-tracking branch 'origin/main' into feat/601-provider-neu…
mogul Oct 27, 2026
6977143
docs(verify): record live validation at 8a594932 with SBX v0.48.0 and…
mogul Oct 27, 2026
4daf57f
feat(kits): generalize provider kits with trusted sources (#642)
mogul Oct 28, 2026
14a95f2
fix(provider-input): port process/path helpers to Windows/MSYS
mogul Oct 26, 2026
9ee0b50
fix(kit-translate): replace empty insteadOf with identity rewrite
mogul Oct 26, 2026
6eda062
fix(msb): hash kit-cache slug to avoid Windows MAX_PATH
mogul Oct 26, 2026
ad7d6ec
fix(provider-input): force LF from the facts reader's Python
mogul Oct 26, 2026
ee2ef2e
fix(provider-input): reject junctions and symlink ancestors on Windows
mogul Oct 27, 2026
bc72881
fix(provider-input): bind snapshot reads to the validated descriptor
mogul Oct 27, 2026
3714490
fix(kit-translate): isolate anonymous fetch from inherited insteadOf …
mogul Oct 27, 2026
1d139ad
fix(provider-input): validate snapshot paths first and test the porta…
mogul Oct 28, 2026
07e43c1
fix(provider-input): walk the literal path instead of an MSYS-resolve…
mogul Oct 28, 2026
5c80304
test(provider-input): name the snapshot shim test for what it asserts
mogul Oct 28, 2026
68bac99
Merge remote-tracking branch 'origin/main' into fix/acq-windows-porta…
mogul Oct 28, 2026
861d677
fix(provider-input): keep the facts reader heredoc parseable by bash 3.2
mogul Oct 28, 2026
077677f
Merge remote-tracking branch 'origin/main' into fix/acq-windows-porta…
mogul Oct 28, 2026
388d2aa
fix(provider-input): use a junction for the Windows current pointer
mogul Oct 29, 2026
2b6f62c
fix(provider-input): restore the current pointer on a failed Windows …
mogul Oct 29, 2026
fa78d73
refactor(test): reuse canonical heredoc scanner
mogul Nov 7, 2026
af652b9
Merge remote-tracking branch 'origin/main' into fix/acq-windows-porta…
mogul Nov 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
426 changes: 362 additions & 64 deletions acq.backends/common.sh

Large diffs are not rendered by default.

26 changes: 20 additions & 6 deletions acq.backends/kit-translate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,31 @@ kit_translate_fetch() {
# url.<x>.insteadOf rewrite — `gh auth login` authenticates the gh CLI,
# not plain git. See docs/KNOWN_FAILURE_MODES.md.
#
# Attempt 1 (anonymous): prompts disabled + inherited credential helper and
# github.com insteadOf rewrite neutralized, so an unauthenticated fetch
# proceeds without prompting.
# Attempt 1 (anonymous): prompts disabled + inherited (system/global) git
# config isolated, so no url.<x>.insteadOf rewrite (e.g. an enterprise
# mirror) or credential helper can redirect or authenticate the fetch.
# Attempt 2 (authed retry): if attempt 1 genuinely fails (a source that
# needs auth / egress-restricted enterprise mirror that needs the rewrite),
# retry with the system git config intact but STILL prompt-disabled, so a
# configured credential helper can supply creds without ever hanging.
_kit_git_fetch() {
# $1 = extra git -c flags (as a single string, word-split intentionally)
local _cfg="$1"
# $2 = non-empty to isolate git from inherited (system + global) config.
local _cfg="$1" _isolate="${2:-}"
(
cd "$destdir" || exit 1
export GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=/bin/echo GCM_INTERACTIVE=never
if [ -n "$_isolate" ]; then
# A command-line identity insteadOf rewrite does NOT shadow an
# inherited equal-length url.*.insteadOf (git's longest-prefix match
# keeps the inherited rewrite winning), and an empty insteadOf value
# makes git prepend the URL to itself. Isolate git from system and
# global config so only the fresh repo's .git/config applies; the
# authed retry below restores inherited config when it is required.
export GIT_CONFIG_NOSYSTEM=1
export GIT_CONFIG_GLOBAL="$destdir/.gitconfig-empty"
: > "$GIT_CONFIG_GLOBAL"
fi
rm -rf .git 2>/dev/null
git init -q
git remote add origin "$url" 2>/dev/null || git remote set-url origin "$url"
Expand All @@ -195,8 +207,10 @@ kit_translate_fetch() {
)
}
local _ferr _anon_cfg
_anon_cfg="-c credential.helper= -c url.https://github.com/.insteadOf="
if _ferr=$(_kit_git_fetch "$_anon_cfg"); then
# Attempt 1 runs with inherited git config isolated (see _kit_git_fetch);
# the credential helper is also explicitly emptied for defense-in-depth.
_anon_cfg="-c credential.helper="
if _ferr=$(_kit_git_fetch "$_anon_cfg" isolate); then
:
elif _ferr=$(_kit_git_fetch ""); then
# authed retry (system config) succeeded
Expand Down
7 changes: 5 additions & 2 deletions acq.backends/msb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1292,8 +1292,11 @@ _acq_msb_fetch_kit() {
git+*|*://*) printf '%s\n' "$ACQ_MSB_KIT_LOCAL_DIR"; return 0 ;;
esac
fi
# Derive a stable cache subdir from the ref (sha + dir tail).
slug=$(printf '%s' "$kitref" | tr -c 'A-Za-z0-9._-' '_' )
# Derive a stable, SHORT cache subdir from the ref. The full sanitized ref
# (URL + sha + dir) can exceed Windows MAX_PATH once git materializes
# .git/objects/pack/* under it ("Filename too long" on fetch), so key the
# cache by a hash of the ref instead — deterministic and collision-resistant.
slug=$(printf '%s' "$kitref" | git hash-object --stdin)
dest="${ACQ_MSB_KIT_CACHE}/${slug}"
if [ -d "$dest/.git" ]; then
# Already fetched — recompute the kit subdir path from the ref's dir=.
Expand Down
20 changes: 20 additions & 0 deletions docs/BACKEND_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,26 @@ otherwise corrupt colon-delimited mounts and guest-only paths. See
[ADR-0029](adr/0029-windows-host-guest-path-forms.md). On macOS/Linux the two
forms are identical.

**Windows provider-input boundary:** on macOS/Linux the host-owned provider-input
tree and the fetched kit are walked with descriptor-relative, no-follow opens, and
the snapshot reads the very descriptor it validated. Native Windows Python has no
equivalent, so `acq` falls back to a component-by-component walk that rejects
symlinks, junctions, and any other reparse point at every level (creating missing
directories one level at a time), and rejects backslash and drive-qualified
manifest paths. Git Bash would otherwise convert a POSIX-looking argument to a
native path before Python sees it, and that conversion resolves an intermediate
junction or symlink, so the walk would never observe it. `acq` therefore runs
these helpers with argument conversion disabled and translates the path to its
native form lexically (only the leading drive or mount component goes through
`cygpath`; everything below it is appended as a string), so the walk checks the
literal path the caller passed. That fallback validates by name and then opens by
name, so it is weaker than the POSIX path: it assumes the staging and projection directories are
private to the current user. It is exercised on a real Windows runner only by the
Windows-guarded tests; the Linux suite covers the same walk's symlink handling by
forcing the portable code path and simulates the argument conversion with a shim.
That real Git Bash honors the conversion opt-out for this call can only be
confirmed on Windows.

### Disposable primary (`--clone`)

msb has no native clone mode, so the neutral `--clone`
Expand Down
41 changes: 38 additions & 3 deletions test/bats/100-kit-translate.bats
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,11 @@ SPEC
refute_regex "$spec" "chmod 0755 '/workspace/w\.sh'"
}

@test "#207: a git+https kit fetch is non-interactive and neutralizes credentials" {
@test "#207: a git+https kit fetch is non-interactive and isolates inherited git config" {
local gitlog="$STUBDIR/git-invocations.log"; : >"$gitlog"
cat >"$STUBDIR/git" <<GITSTUB
#!/usr/bin/env bash
printf 'PROMPT=%s ARGS=%s\n' "\${GIT_TERMINAL_PROMPT:-UNSET}" "\$*" >>"$gitlog"
printf 'PROMPT=%s NOSYS=%s GLOBAL=%s ARGS=%s\n' "\${GIT_TERMINAL_PROMPT:-UNSET}" "\${GIT_CONFIG_NOSYSTEM:-UNSET}" "\${GIT_CONFIG_GLOBAL:-UNSET}" "\$*" >>"$gitlog"
case "\$*" in
*fetch*) exit 0 ;;
*checkout*) mkdir -p "integrations/isolation/acq-kits/usai-provider"; exit 0 ;;
Expand All @@ -247,7 +247,42 @@ GITSTUB
assert_regex "$fetchline" 'PROMPT=0'
anonline=$(grep -m1 'credential.helper=' "$gitlog" || true)
assert_regex "$anonline" 'credential\.helper='
assert_regex "$anonline" 'insteadOf='
assert_regex "$anonline" 'NOSYS=1'
assert_regex "$anonline" 'GLOBAL=.*\.gitconfig-empty'
}

@test "#207: an equal-length global insteadOf mirror rewrite is neutralized for the anonymous attempt" {
# A global `url.https://mirror.invalid/.insteadOf=https://github.com/` has the
# SAME prefix length as the old command-line identity rewrite, so git's
# longest-prefix match kept the inherited mirror rewrite winning and the
# anonymous fetch was redirected to the mirror. Isolating git from inherited
# config (GIT_CONFIG_NOSYSTEM + empty GIT_CONFIG_GLOBAL) neutralizes it.
local realgit="$ACQ_TEST_REAL_GIT" home="$STUBDIR/fakehome" resolved="$STUBDIR/resolved-url"
mkdir -p "$home"
"$realgit" config --file "$home/.gitconfig" url.https://mirror.invalid/.insteadOf https://github.com/

# A git that resolves the origin URL (as a fetch would) with the REAL git under
# the fetch's own environment, records the result, and fakes a successful fetch.
cat >"$STUBDIR/git" <<GITSTUB
#!/usr/bin/env bash
original=("\$@")
while [ "\$#" -gt 0 ]; do case "\$1" in -c) shift 2 ;; *) break ;; esac; done
case "\${1:-}" in
fetch) "$realgit" remote get-url origin > "$resolved"; exit 0 ;;
checkout) exit 0 ;;
*) "$realgit" "\${original[@]}" ;;
esac
GITSTUB
chmod +x "$STUBDIR/git"

run bash -c '
export HOME="'"$home"'"
. "'"$REPO_ROOT"'/acq.backends/kit-translate.sh"
kit_translate_fetch "git+https://github.com/GSA-TTS/agentic-coding-patterns.git#ref=deadbeef&dir=integrations/isolation/acq-kits/usai-provider" "'"$STUBDIR"'/kitdest" >/dev/null 2>&1
'

assert_success
assert_equal "$(cat "$resolved" 2>/dev/null || printf 'UNRESOLVED')" "https://github.com/GSA-TTS/agentic-coding-patterns.git"
}

@test "#207: authenticated retry diagnostics never expose credential-bearing URLs" {
Expand Down
Loading
Loading