Skip to content

feat(acq): interactive acq configure UI for extra kits + token-scoping defaults - #500

Merged
mogul merged 9 commits into
mainfrom
feat/interactive-kit-config-ui
Oct 2, 2026
Merged

mogul merged 9 commits into
mainfrom
feat/interactive-kit-config-ui

Conversation

@mogul

@mogul mogul commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a lightweight, dependency-free interactive configuration UI for acq,
modeled on the friendly multiselect installers users like (e.g. the Matt Pocock
skills installer). It makes opt-in kits discoverable and durable, and gives the
per-sandbox GitHub-token-scoping prompt a configurable default.

AI-assisted (OpenCode). Human-reviewed.

Resolves #499.
Implements ADR-0031.

What changed

  • acq.backends/prompt.sh (new) — acq_prompt_multiselect +
    acq_prompt_confirm. Stderr-only chrome, TTY-gated ([ -t 0 ]/[ -t 2 ]),
    colored with the same palette as install.sh, bash-3.2 safe, caller labels
    escape-sanitized (as in progress.sh). Honors ACQ_NO_PROMPT and an
    ACQ_PROMPT_TEST_INPUT test hook. No new pinned dependency — consistent
    with the repo's "one clone, no extra deps" posture.
  • acq configure (new command) — shows the current config, runs the opt-in
    kit multiselect, then a confirm for the token-scoping default, and persists to
    ~/.config/acq/config.yaml. Auto-offered once on first run.
  • config.yaml — the single-key backend: awk parser is generalized to a
    flat-key reader/writer (_acq_config_read_field/_acq_config_write_field);
    adds extra_kits: and scope_github_token: while preserving backend:.
  • Opt-in kit catalog — openchamber, paseo, sourced from the already-pinned
    patterns bundle (PATTERNS_KIT_REF/PATTERNS_KIT_DIR). The four built-in kits
    stay mandatory (shown as informational, not toggleable). prime-agent is
    intentionally omitted — it is a skeleton at the current pin.
  • acq create/run — pre-populates the picker from the global defaults and
    folds the selection into ACQ_EXTRA_KITS, persisted per-sandbox via the
    existing kit-record path (ADR-0017) and reloaded on resume. An exported
    ACQ_EXTRA_KITS still wins (env > config).
  • Token-scoping default — only pre-answers the existing per-sandbox scoping
    prompt; ADR-0013's fine-grained-PAT minting is unchanged.
  • Docs — docs/CONCEPTS.md, docs/howto/acq.md, CONTRIBUTING.md.

Design / scope decisions

Verification

  • Offline unit suite: ./scripts/test-acq-bats — 493/493 pass (adds
    test/bats/135-configure.bats, 19 cases: config round-trip + key preservation
    • fail-closed key names, catalog/ref pinning, prompt widgets via
      ACQ_PROMPT_TEST_INPUT, acq configure dispatch/--help/non-interactive
      fail-open, env-wins precedence).
    • Note on the 90-sbx-startup-kit playbook-probe test: it is a pre-existing
      issue on main (it fails whenever the runner's real $HOME carries a global
      git identity — ~/.gitconfig with user.name/user.email — leaking into the
      stub). It is already fixed by the open PR
      fix(acq): use sandbox workspace for github scoping #476
      , which adds HOME/XDG_CONFIG_HOME
      isolation to that test; verified green there. Not addressed here to avoid a
      duplicate/conflicting edit to the same test file — this PR's suite goes green
      once fix(acq): use sandbox workspace for github scoping #476 merges (or under a neutralized HOME). An earlier note also
      mentioned 70-msb-backend; that was a local-environment false alarm (a stray
      ~/.config/acq/config.yaml from ad-hoc testing), not a real failure — feat(bootstrap): add init-project script for project bootstrapping #70
      passes on main and on this branch.
    • The backend unset change here also removes a now-empty config.yaml so a
      cleared config looks pristine (and first-run detection still fires).
  • ShellCheck (--severity=warning, matching .shellcheckrc): clean on
    acq, acq.backends/prompt.sh, acq.backends/common.sh.
  • bash -n syntax check clean on all modified shell files.

Rollback

Revert this PR. The new command and config keys are additive; existing
--kit/ACQ_EXTRA_KITS/backend: behavior is unchanged, and non-interactive
runs behave exactly as before (fail-open to defaults).

Security impact

  • No new runtime dependency (SR-3).
  • No secrets on argv; GitHub PAT minting stays on the existing masked
    per-sandbox path (AC-6, IA-5, ADR-0013 unchanged).
  • All interactive chrome is TTY-gated; CI/piped runs are unaffected.

…defaults

Records the decision to add an interactive `acq configure` UI: a hand-rolled,
dependency-free multiselect (modeled on progress.sh/install.sh) plus a new
command, config.yaml defaults, and per-sandbox deviations at create time. The
built-in kits stay mandatory; the token-scoping preference only pre-answers the
existing ADR-0013 per-sandbox scoping prompt.

Refs: #499
Add a lightweight, dependency-free interactive UI (modeled on install.sh
colors and progress.sh conventions) for configuring opt-in kits and the
per-sandbox GitHub-token-scoping default.

- New acq.backends/prompt.sh: acq_prompt_multiselect + acq_prompt_confirm.
  TTY-gated, install.sh color palette, bash-3.2 safe, escape-sanitized,
  ACQ_NO_PROMPT opt-out, ACQ_PROMPT_TEST_INPUT test hook. Non-interactive /
  CI degrades to defaults and never blocks.
- New `acq configure` command; auto-offered on first run. Persists choices to
  config.yaml (generalized flat-key reader/writer; preserves the backend key).
- Opt-in kit catalog (openchamber, paseo) sourced from the pinned patterns
  bundle; four built-in kits stay mandatory. prime-agent omitted (skeleton at
  the current pin).
- At acq create/run, the picker is pre-populated from the configured defaults;
  the selection folds into ACQ_EXTRA_KITS and is persisted per-sandbox via the
  existing kit-record path (env-supplied ACQ_EXTRA_KITS still wins).
- The token-scoping preference only pre-answers the existing per-sandbox
  scoping prompt; ADR-0013 fine-grained-PAT minting is unchanged.
- Docs (CONCEPTS.md, howto/acq.md) + offline bats coverage (135-configure.bats).

Refs: #499
Implements: ADR-0028
@mogul
mogul requested a review from a team as a code owner September 20, 2026 19:59
@mogul

mogul commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Demo: acq configure interactive UI

Here's the interactive picker this PR adds, recorded from a real acq configure session (PTY-driven, scratch XDG_CONFIG_HOME, no backend/network/secrets):

acq configure demo

The clip shows the arrow-key/space multiselect over the opt-in kits
(openchamber, paseo — the four built-ins stay mandatory and aren't listed),
then the [y/N] GitHub-token-scoping default prompt, then the saved-config
summary.

GIF lives on a media-only orphan branch (media/pr-500-configure-demo) so no binary lands in this feature branch or main. AI-assisted (OpenCode).

Instead of a separate "Built-in kits (always applied): ..." banner above the
selection area, render the four built-ins inline as the top rows of the same
multiselect: always-checked, dimmed, tagged "(always applied)", cursor-skipped,
and never toggleable.

- acq_prompt_multiselect gains a leading LOCKED_N arg: the first N rows are
  frozen. DEFAULTS_CSV and the returned indices are relative to the toggleable
  block, so callers' index<->name mapping is unchanged. Cursor starts on (and
  wraps within) the toggleable rows; SPACE guards against locked rows.
- Add ACQ_KIT_DESCS (one-liners) for the four built-ins.
- acq_configure and create_time_kit_picker prepend the built-ins as locked rows
  and drop the standalone banner.
- Update docs (CONCEPTS, ADR-0028) and tests (LOCKED_N arg + a frozen-row test).

Refs: #499
@mogul

mogul commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Updated demo: built-in kits now shown as frozen rows

Per review feedback, the built-in kits are no longer listed in a separate banner
above the picker — they now appear inline as frozen rows at the top of the
same multiselect: always-checked, dimmed, tagged (always applied), and the
cursor skips over them (they can't be toggled).

acq configure demo

The clip shows the four frozen built-ins, then the arrow-key/space multiselect
over the opt-in kits (openchamber, paseo), the [y/N] token-scoping
prompt, and the saved-config summary.

Recorded from a real PTY-driven acq configure session (scratch XDG_CONFIG_HOME, no backend/network/secrets). GIF on the media-only branch media/pr-500-configure-demo. AI-assisted (OpenCode).

Each row is redrawn in place by moving the cursor up N rows (N = row count).
A row wider than the terminal soft-wrapped onto a second physical line, so the
up-by-N math landed a line short and the top row (zscaler) visibly duplicated
and shoved the rest down on every keypress.

Fit each row (and the header) to the terminal width before printing: assemble
the row as one plain string, truncate to COLUMNS with a trailing ellipsis, then
wrap the whole thing in a single color span (color codes are zero-width, so the
physical row stays one line). Width comes from COLUMNS/tput, defaulting to 80.

Also simplifies per-row coloring to a single span (was per-field), which both
guarantees the one-line invariant and avoids attributes bleeding across a
truncation boundary.

Adds a regression test asserting no rendered row exceeds COLUMNS.

Refs: #499
@mogul

mogul commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Fixed: picker no longer duplicates the top row

Good catch — that was a real rendering bug, not just a recording glitch. Rows
wider than the terminal soft-wrapped onto a second physical line, which desynced
the in-place repaint (it moves the cursor up by the row count), so zscaler-…
duplicated and pushed the rest down on each keypress.

Fix: each row (and the header) is now fit to the terminal width before
printing — truncated with a trailing … when needed — so every row stays on
exactly one physical line and the repaint math holds. Re-recorded demo:

acq configure demo

Regression test added (no rendered row exceeds COLUMNS). GIF on media/pr-500-configure-demo. AI-assisted (OpenCode).

@wz-gsa

wz-gsa commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

AI-assisted review (OpenCode), advisory — needs a human to confirm before it drives a change. Reviewed at head 444c67f, main at 8e6160c.

The picker layer is careful work — control-byte sanitizing on caller data, a real non-interactive path that makes no changes, terminal-width fitting, and 21 tests that drive the widget offline via ACQ_PROMPT_TEST_INPUT. Four things, one of which will break on merge.

Blocker: duplicate ADR number

This PR adds docs/adr/0028-interactive-acq-configure.md, but main already has docs/adr/0028-windows-native-secret-storage.md (merged in #464 on 09-21) and 0029-windows-host-guest-path-forms.md.

The branch point explains it — the merge-base is 0c399c56, which predates both. Nothing conflicts textually, so git will merge it happily and leave two different ADR-0028s in docs/adr/. Every cross-reference to "ADR-0028" then becomes ambiguous, and the PR body plus CONTRIBUTING.md/CONCEPTS.md/howto/acq.md all cite it by number. Next free number is 0031 (main has ADR-0030 landing in #504).

Should-fix: config.yaml is world-readable, and the reader is YAML-unaware

Two separate issues in the same file, which now holds durable configuration rather than one backend: line.

Mode. _acq_config_write_field (acq.backends/common.sh:1162-1195) does mkdir -p then a temp+rename with no umask or chmod. Measured under umask 022:

$ stat -f '%Lp %N' config.yaml
644 …/acq/config.yaml

Not currently a secret — extra_kits and scope_github_token are preferences — but this file is now the durable place acq stores what it should do, and secret-store.sh already wraps its state writes in ( umask 077; … ). Cheap to match: ( umask 077; mkdir -p "$dir" ) plus chmod 600 on the temp before the rename.

Parse. _acq_config_read_field (:1135-1153) matches ^[[:space:]]*KEY[[:space:]]*:, so leading indentation is ignored. A nested key reads as top-level:

some_block:
  extra_kits: nested-evil
$ _acq_config_read_field extra_kits   ->  nested-evil

It also stops at the first match (exit), so with duplicate keys the first wins while _acq_config_write_field drops all matching lines and appends one — read and write disagree on which line is authoritative. Both are fine for a flat file acq wrote itself; the comment calls it "defensive awk parse, no YAML dependency", and the risk is that a user hand-edits the file into something structured and the reader confidently misreads it. Worth either anchoring to column 0 (^KEY[[:space:]]*:) or saying in the docs that the file must stay flat.

Should-fix: the pass-through branch persists an unvalidated kit ref

_acq_apply_configured_extra_kits (common.sh:133-161):

# (a name not in the catalog is passed through as-is, tolerating a hand-edited
# config that used a raw ref).
…
else
  ref="$tok"
fi

So a config.yaml containing extra_kits: git+https://evil.example/x.git#ref=…&dir=… is expanded into ACQ_EXTRA_KITS and fetched. I traced where a ref actually lands: kit_translate_fetch (acq.backends/kit-translate.sh:54-95) validates dir= against traversal and an illegal-character set, and requires both ref= and dir= — but nothing validates the host or URL, and KIT_SOURCE_PREFIXES never reaches that file (grep: zero hits in kit-translate.sh, on this branch and on main). The prefixes are pushed into sbx settings set kit.allowedSources (sbx.sh:145,:169) — an allowlist enforced by sbx for its fetches, not a check acq applies to its own.

The missing host check is pre-existing, not this PR's — ACQ_EXTRA_KITS has always accepted an arbitrary ref. What changes here is durability and provenance: previously a hostile ref had to be re-supplied by the user each run via the environment; now it can sit in config.yaml and apply to every invocation silently. That is worth a deliberate decision rather than a parenthetical. Either validate a pass-through token against KIT_SOURCE_PREFIXES before using it, or refuse unknown names and require ACQ_EXTRA_KITS for raw refs (the comment already says custom refs "are layered separately and are not managed here" — the code doesn't hold that line).

Nit: the escape sanitizer misses 8-bit C1 controls

_acq_prompt_sanitize (acq.backends/prompt.sh:54-56) strips \000-\037 and \177. I transcribed it and confirmed it defeats the realistic cases — raw ESC[31m, CSI 2J, OSC 8 hyperlinks, BEL, and \r overwrite all get neutered. But bytes ≥ 0x80 pass:

input 'a\233' '1mB'  ->  a M-^[ 1mB     # 0x9b = 8-bit CSI, survives

tr -d '\200-\237' closes it. Low severity: it needs a terminal in an 8-bit mode, and the kit descriptions here are repo-authored — but the comment's claim is "an untrusted kit description can't inject terminal escapes", and with ACQ_EXTRA_KITS pointing at a fetched kit, "untrusted" is the accurate word.

Verified as holding

  • The non-interactive path makes no changes. acq_configure (common.sh:2765-2775) returns early with a message when _acq_prompt_interactive is false, explicitly so it can't clobber a hand-edited file with widget defaults. That is the degraded-path rule applied correctly — "could not prompt" is its own state, not a silent default-write.
  • _acq_config_write_field and _acq_config_read_field both fail closed on an unsafe KEY (''|*[!A-Za-z0-9_]*), so a caller can't inject an awk regex.
  • The write is atomic (temp + mv -f), and an empty VALUE removes the key rather than writing an empty-valued line — so "no extras" is absence, which the reader handles.
  • extra_kits stores opt-in names, not expanded refs, so a later PATTERNS_KIT_REF bump moves configured kits forward. Good call.
  • Env beats config: _acq_apply_configured_extra_kits returns early when ACQ_EXTRA_KITS_FROM_ENV is set, matching the documented precedence.
  • 21 tests, driven offline through ACQ_PROMPT_TEST_INPUT, with colour suppressed so assertions match plain text. Only one negative assertion in the file (135-configure.bats:285) and it has a positive on the same subject immediately before it (:284) — no vacuous-refute risk.
  • bin/injection-scan.sh clean.

Method: static analysis at 444c67f. No PR code executed — the sanitizer, config reader, and config writer results are from my own verbatim transcriptions of those functions, run against scratch fixtures. UNVERIFIED: the picker's actual rendering, including the row-fitting fix in 444c67f (the demo GIFs are the evidence there, and I did not drive a PTY).

@mogul

mogul commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in commit 3d166a0 and ready for re-review.

Changes made:

  • Renumbered the new ADR from ADR-0028 to ADR-0031 to avoid the main-branch ADR collision, and updated references.
  • Hardened config.yaml handling:
    • top-level-only flat-key parsing, so nested YAML keys are ignored;
    • duplicate top-level keys now use last-wins behavior, matching writer replacement behavior;
    • config dir/file writes are private (700 dir, 600 file/temp).
  • Changed persisted config extra_kits to fail closed on unknown names/raw refs. Custom refs still work via ACQ_EXTRA_KITS or --kit, but are not accepted from durable config.
  • Propagated that fail-closed result through create and fresh run before backend provisioning.
  • Extended prompt sanitization to strip 8-bit C1 controls as well as C0/DEL.
  • Updated ADR-0031 to document the stricter persisted-config behavior.

Additional tests added:

  • nested config key ignored + duplicate top-level last-wins;
  • config permissions under permissive umask;
  • sanitizer strips C0/C1/DEL;
  • unknown persisted extra_kits fail closed;
  • create aborts before backend create on unknown persisted extra_kits.

Verification:

  • bash -n acq acq.backends/.sh scripts/test-acq-lib.sh scripts/test-acq-bats test/bats/.bats test/bats/helper.bash: pass
  • shellcheck --severity=warning over acq, backend scripts, test runner, and bats files: pass
  • ./scripts/test-acq-bats test/bats/30-dispatch-routing.bats test/bats/135-configure.bats: 56/56 pass
  • HOME="$PWD/.tmp-acq-test-home-full" XDG_CONFIG_HOME="$PWD/.tmp-acq-test-home-full/.config" ./scripts/test-acq-bats: 500/500 pass
  • npm run lint: pass, markdownlint 0 issues

Note: a non-isolated full ./scripts/test-acq-bats still reproduces the pre-existing 90-sbx-startup-kit HOME/gitconfig leak described earlier; the same test file passes with isolated HOME. AI-assisted (OpenCode).

@basiliskus basiliskus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 3d166a0.

I reviewed this with the change-review skill, focusing on the prior advisory items plus the create/run wiring. The hardening direction is good, but I found one functional regression that should be fixed before merge.

  1. Major: configured extra kits are not applied on sbx fresh creates/runs. acq:944, acq:1072, acq.backends/sbx.sh:260
    A user can select openchamber in acq configure, then run ACQ_BACKEND=sbx acq create opencode ., but the selected kit never reaches sbx create. acq_resolve_backend builds KITS before dispatch; _acq_apply_configured_extra_kits and create_time_kit_picker mutate ACQ_EXTRA_KITS later, but nothing rebuilds KITS before sbx emits --kit flags from for k in "${KITS[@]}". msb reads current ACQ_EXTRA_KITS directly during provision, so the bug is backend-specific. This also explains why the new tests can pass: they assert the helper mutates ACQ_EXTRA_KITS, but do not assert a positive configured-extra sbx create --kit ...openchamber... call.

  2. Minor: the C1 sanitizer corrupts valid UTF-8 text. acq.backends/prompt.sh:56
    The fix strips raw bytes 0x80-0x9f under LC_ALL=C. Those byte values are also valid UTF-8 continuation bytes, so normal text like don’t, …, or naïve is rendered as invalid/mangled UTF-8. I reproduced the byte-level behavior with the same tr -d '\000-\037\177\200-\237' range. The C1 escape concern is real, but this implementation damages legitimate non-ASCII labels/descriptions.

  3. Minor: config.yaml still breaks on common valid YAML syntax. acq.backends/common.sh:1154
    The parser now correctly ignores nested keys and uses last-wins for duplicate top-level keys, but it still returns inline comments and quotes literally. For example, extra_kits: openchamber # browser UI is parsed as openchamber # browser UI, causing fail-closed unknown-kit errors on #, browser, and UI; extra_kits: "openchamber paseo" similarly fails on quoted tokens. Since this file is named config.yaml, hand-edited YAML comments/quoted scalars are a realistic failure mode.

Below the bar: the PR body still says Implements [ADR-0028](docs/adr/0028-interactive-acq-configure.md) even though the branch now uses ADR-0031. The in-repo references I checked are consistently ADR-0031, and 0031 does not currently conflict with main or the other open ADR-number claims called out in the review request. Also, very narrow terminals remain capable of wrapping rows because COLUMNS<20 is replaced with 80, but I would not block on that alone.

Validation: gh pr checks 500 shows all five checks passing, and the requested ShellCheck command completed cleanly with no output. I did not run the full local bats suite or npm run lint:md because this review session was under read-only plan-mode constraints that prohibit write-producing commands/cache/temp-file setup.

Verdict: request changes.

Assisted-by: OpenCode [gpt_5_5_default_v2] change-review

@mogul

mogul commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the latest review feedback in 3e495ef and brought the branch up to date with main.

Changes made:

  • Rebuilt the kit list after configured defaults/create-time picker mutate ACQ_EXTRA_KITS, so configured opt-in kits now reach sbx fresh create and fresh run --kit flags.
  • Updated the config reader to tolerate common hand-edited flat YAML forms: inline comments and simple single/double-quoted scalars, while still ignoring nested keys and using last top-level key wins.
  • Reworked prompt sanitization so valid UTF-8 text is preserved while raw C0/DEL controls, invalid UTF-8 raw C1 bytes, and UTF-8 encoded C1 controls are removed.
  • Resolved the merge with current main, including the github-scope workspace-record changes.
  • Updated the PR body's ADR reference from ADR-0028 to ADR-0031.

Adversarial review:

  • Ran a sub-agent adversarial code review against the updated working tree.
  • It found the merge index still needed resolution, the UTF-8 encoded C1 sanitizer gap, and missing fresh-run SBX coverage.
  • Those findings were addressed before push.

Verification:

  • bash -n acq acq.backends/common.sh acq.backends/prompt.sh acq.backends/sbx.sh acq.backends/msb.sh scripts/test-acq-lib.sh scripts/test-acq-bats test/bats/30-dispatch-routing.bats test/bats/135-configure.bats test/bats/helper.bash: pass
  • shellcheck --severity=warning over acq, backend scripts, test runner, and bats files: pass
  • ./scripts/test-acq-bats test/bats/30-dispatch-routing.bats test/bats/135-configure.bats: 63/63 pass
  • HOME="$PWD/.tmp-acq-test-home-full" XDG_CONFIG_HOME="$PWD/.tmp-acq-test-home-full/.config" ./scripts/test-acq-bats: 558/558 pass
  • npm run lint: pass, markdownlint 0 issues
  • git diff --check: pass

Ready for re-review. AI-assisted (OpenCode).

basiliskus
basiliskus previously approved these changes Oct 2, 2026

@basiliskus basiliskus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 3e495efe2d807f448076dfac54ebefcdec26e8cc.

The blocker from my earlier pass is fixed, and I verified it by reproduction on both backends rather than by reading the diff. Clearing my CHANGES_REQUESTED.

The fix is _build_kit_list being re-run after the configured defaults and the create-time picker mutate ACQ_EXTRA_KITS (acq:948 for create, acq:1077 for fresh run). That is the right place: KITS was previously built once during acq_resolve_backend, before the config was ever read, and the sbx provisioner iterates KITS.

Probes against the real harness, with extra_kits: openchamber in the harness's own $XDG_CONFIG_HOME/acq/config.yaml:

sbx create --kit ... openchamber        -> present (count 1)
msb: acq-kits/openchamber applied       -> present
contrast, no config                     -> absent on both

I also checked the failure mode the rebuild introduces, since re-running a list builder after mutation is the kind of fix that double-adds:

openchamber occurrences on the sbx create line: 1

No duplicate --kit flag, and kit order is unchanged — which matters here because later kits can override earlier ones.

Prior findings, verified at this head:

  1. Configured extra kits never reached the backend — resolved. Reproduced fixed on both sbx and msb, per above. Previously this was 0 on both, while env-supplied ACQ_EXTRA_KITS and --kit both worked — i.e. only the configured path was inert, which was the feature's whole point.
  2. Sanitizer corrupted valid UTF-8 — resolved. _acq_prompt_sanitize now round-trips through iconv -f UTF-8 -t UTF-8 -c and strips UTF-8-encoded C1 (C2 80-9F) rather than deleting raw bytes in 0x80-0x9f. don’t café naïve … survives intact; raw ESC, raw C1, UTF-8 C1 and DEL are still removed. The earlier don’t → don\342t corruption is gone.
  3. Config reader returned comments and quotes literally — mostly resolved. Inline comments are stripped and simple fully-quoted scalars unwrapped; nested keys still ignored, last top-level key still wins. test/bats/135-configure.bats:104-118 covers the three forms. One edge remains, below.
  4. COLUMNS<20 replaced with 80 — not resolved. Still a nit, still not worth blocking.

Below the bar:

  • acq.backends/common.sh:1167-1183 strips inline comments before unwrapping quotes, so a valid YAML scalar like extra_kits: "openchamber # browser UI" is truncated to "openchamber and then fails closed on an unknown kit token. Only reachable by hand-editing, and failing closed is the safe direction — but the file is named config.yaml and the PR says quoted scalars are tolerated, so it is worth either tightening the awk order or narrowing the claim.
  • acq.backends/prompt.sh:92-103: _acq_prompt_term_cols returns 80 for any COLUMNS under 20, so a 10-column terminal still gets 80-column rows and can soft-wrap into the repaint desync this code exists to avoid. Confirmed by direct probe: COLUMNS=1, 10, 19 → 80; COLUMNS=20 → 20. Sub-20-column terminals are rare enough that I would leave it.
  • ADR numbering is clean: main has 0028/0029, this branch adds docs/adr/0031-interactive-acq-configure.md, and the in-PR cross-references point at 0031. Worth knowing the neighbourhood moved while this sat — #504 is now ADR-0035 and #510 claims 0032, so 0031 is uncontested.

Validation. Ran: full suite ./scripts/test-acq-bats → 558 ok / 0 not ok / 2 skips (both skips are pwsh not available), matching your reported count; three throwaway bats probes against the real harness for the configured-kit path on both backends plus the duplicate check (removed afterwards, git status --porcelain clean); direct function probes for the sanitizer and _acq_prompt_term_cols; bash -n over the changed shell files; the repo ShellCheck batch per AGENTS.md (clean — shellcheck is present in my environment, so that is a real pass); npm run lint:md → 0 issues; gh pr checks 500 all passing.

Not run: anything requiring a live backend or a real PTY. This is ~1600 lines of new interactive terminal code and my evidence is entirely stubbed — the widget's actual rendering, key handling, repaint behaviour on a real terminal, and the first-run flow are unverified by me. Worth one manual pass through acq configure on a real terminal before relying on it, and that is the main thing this approval does not cover.

Verdict: approve.

Assisted-by: OpenCode [gpt_5_5_default_v2] change-review

@mogul

mogul commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Addressed the quoted-config review point. The flat config reader now preserves # inside quoted scalars while still stripping unquoted inline comments; added focused coverage for extra_kits: "openchamber # browser UI" plus plain-scalar comment behavior.

Verification: ./scripts/test-acq-bats test/bats/135-configure.bats, shellcheck --severity=warning acq.backends/common.sh test/bats/135-configure.bats, and bash -n acq.backends/common.sh acq test/bats/135-configure.bats all pass.

Given the small blast-radius of the change I'm going to exercise Maintainer's Right to skip getting another review round done.

@mogul
mogul merged commit 13d9d3a into main Oct 2, 2026
7 checks passed
@mogul
mogul deleted the feat/interactive-kit-config-ui branch October 2, 2026 22:18
basiliskus pushed a commit that referenced this pull request Oct 3, 2026
* fix(msb): block unsafe 0.7 migration versions

* Update files

* feat(msb): add 0.7.3 forward recovery, pinned Windows install, and live verification

Interim checkpoint of the msb blocked-version work. The installer side and the
live verification are complete; docs, ADR-0031, and the bats installer stubs are
still outstanding (7 tests in test/bats/05-install.bats still assert the removed
upstream-installer path and currently fail).

msb 0.7.3 shipped mid-change with the upstream cross-version compatibility fix
(microsandbox#1634, closing microsandbox#1616), which is the reason the
0.7.0-0.7.2 range was blocked at all. That changes the recommended recovery from
"roll back to 0.6.18" to "move forward to 0.7.3", so:

- Add MSB_FIXED_VERSION=0.7.3 to install.sh and acq.backends/msb.sh, and offer
  `msb self update` FIRST for a blocked msb whose catalog was already migrated.
  The migration sets are additive, so the fixed line reads an already-migrated
  catalog with no rollback, no backup dance, no data-affecting step, and none of
  the refusals a downgrade can hit. The rollback to the pinned version remains as
  the fallback. The result is re-checked rather than assumed, because
  `msb self update` targets whatever is newest.
- Drop the ">= 0.7.3 once available" phrasing throughout; it is available.

Guard the wedge discovered while verifying the recovery. A `msb self downgrade`
attempted by too-old an msb fails AND leaves an operation journal that makes
every later msb command from every version refuse, with no upstream flag to
abort or resume it. So:

- recover_migrated_catalog now refuses to drive a rollback with a binary from
  older than the blocked line, making the wedge unreachable through acq.
- msb_stale_downgrade_journal / clear_stale_downgrade_journal detect a
  pre-existing wedge and offer to remove only the operation directory, never the
  database, retained backups, or sandbox state. This runs before any msb probe,
  since a wedged install would otherwise make msb_catalog_ahead misread.

Close the Windows gap. install.ps1 had no msb version guard at all, so Windows
hosts could land on a blocked release via the unpinned one-line installer:

- Add the version policy constants, a component-wise comparator (not [version],
  so a build suffix cannot throw mid-install), and blocked/too-old/unparseable
  handling that mirrors install.sh, including the forward-to-0.7.3 path.
- Install the pinned msb from its release bundle verified against the release's
  own checksums.sha256, rather than piping the upstream installer into
  Invoke-Expression and trusting it to honor an environment variable we cannot
  verify. The Windows bundle is two files, so placing them directly is checkable.
- Check for an in-use msb.exe before copying, since Windows cannot overwrite a
  running image, and fail with an actionable message instead of a sharing
  violation mid-copy.
- Verify the active msb after every install path, including WinGet, whose package
  version is outside our control.
- Extract Add-UserPath from Ensure-Path so the msb bin dir gets the same
  ask-before-changing-PATH treatment as the acq install dir.

Add scripts/verify-msb-pin, which verifies all of the above against real msb
binaries instead of mocks. It runs each check against a throwaway MSB_HOME *and*
HOME -- both are required, because `msb self downgrade` writes command symlinks
into $HOME/.local/bin and would otherwise replace the caller's real msb link. It
boots no VM, so unlike the other verify-* scripts it needs no virtualization and
can run inside a sandbox. Verified 24/24 on linux-aarch64 and darwin-arm64:

- the upstream installer cannot pin (per-release install.sh assets are
  byte-identical and resolve releases/latest at run time)
- the pinned bundle verifies against its published checksum and runs
- 0.6.18 -> blocked 0.7.x -> 0.6.18 reproduces the reported stranding
- 0.7.3 reads that already-migrated catalog directly
- `self downgrade` driven BY the blocked binary restores it, retaining a backup
- the same attempted by the older binary fails, wedges every msb command, and is
  cleared only by removing the journal directory
- a keg-only formula's layout runs by path, with no LD_LIBRARY_PATH and no
  signature-invalidating binary edit, and can drive the recovery unlinked

Also stop probing keg-only brew formulae in msb_candidate_paths: they are never
symlinked into the prefix and so cannot shadow anything, and reporting them would
turn a correct side-by-side install into a spurious "multiple msb binaries"
warning.

Refs: #510
Refs: GSA-TTS/homebrew-tap@475cce3

* docs(msb): add ADR-0032, fix the installer test stubs, and reconcile with upstream's migration guide

Completes the msb blocked-version work checkpointed in 97226fa. That commit landed
the installer and the live verification but left the ADR unwritten, the docs stale,
and 7 tests asserting a code path it had removed. Upstream also published a v0.6->v0.7
migration guide in the meantime, which changes what this can claim and what it must
reconcile against.

ADR-0032 (renumbered from 0031, which PR #500 already
owns) records the version policy, the pinned+checksummed install, the forward-first
recovery, and the wedge guards. It cites upstream's guide as primary evidence where one
now exists, and keeps the migration-prefix analysis as independent corroboration --
re-derived from upstream source at each tag rather than carried over: the applied
migration lists are strict prefixes (0.6.18 has 25, 0.7.2 adds split_snapshot_identity
and snapshot_groups, 0.7.3 adds migrate_secret_config), which is why the fixed line
opens an already-migrated catalog with no rollback.

Reconcile the journal guard with upstream's instruction. The guide says "Do not delete
the catalog or edit migration history to bypass the refusal", and clear_stale_downgrade_journal
removes a directory under $MSB_HOME/db. That is a different action and the code, the ADR,
and the failure-mode doc now say so explicitly rather than leaving a reader to infer it:
the journal is a lock whose `phase != complete` means an operation is in flight, msb's own
downgrade path retires it on success, and removing it leaves a bit-identical catalog. What
upstream warns against -- deleting the database or editing the applied-migrations table --
acq never does.

Fix the 7 failing tests in test/bats/05-install.bats. They asserted the upstream-installer
path that install_msb_pinned_tarball replaced, so they were testing a string that no longer
exists rather than the behavior. They now build a stand-in release bundle plus a matching
checksums.sha256 and drive the real install: platform-derived bundle name, the real
libkrunfw filename shapes (.so.5.6.1 / .5.dylib) so the ABI-derivation code is exercised,
and a curl stub that hard-errors on any unexpected URL. Three new cases cover the
fail-closed paths that had none: a checksum mismatch, a release with no checksum entry for
our asset, and a source-level assertion that no fetched artifact is handed to a shell.

Six defects found reviewing the checkpoint:

- MSB_ALREADY_SUPPORTED was assigned in two branches and read nowhere.
- verify_active_msb_pinned reported "msb 0.6.18 was installed" after the forward path had
  installed 0.7.3. Renamed to verify_active_msb_supported and de-versioned its messages.
- msb_report_candidates accumulated an unused `versions` string and probed --version on
  every candidate before the single-candidate early return.
- scripts/verify-msb-pin was committed non-executable, so `./scripts/verify-msb-pin` --
  the command install.sh and msb.sh both print -- failed with Permission denied.
- verify-msb-pin gave a misleading failure on a small TMPDIR: it needs ~1.2GB, and
  exhausting the filesystem surfaced as `msb self downgrade` reporting "failed to unpack
  libkrunfw", indistinguishable from a real defect in the path under test. It now checks
  up front and says so.
- The version-drift check reached its expected value through `eval`, tripping SC2154. It
  now passes values directly, and covers MSB_BLOCKED_VERSION_MAX as well.

Also document the 0.7.3 interaction this PR is what makes reachable: upstream enabled
--net-strict by default there, so a hostname allow rule requires inspectable request
authority. acq's tiers are hostname-based and acq enables --tls-intercept by default, so
the default path is correct -- but ACQ_MSB_NO_TLS_INTERCEPT=1 on 0.7.3+ now makes every
allowed host unreachable rather than merely unsubstituted.

verify-msb-pin gains check 5b for the snapshot-group refusal, which is the load-bearing
reason the rollback cannot be treated as always-available: every 0.7 capture creates a
group by default, and a group blocks the downgrade even when empty. Verified fail-closed
too -- no backup written, no journal left, msb still usable.

Verification (linux-aarch64):
- bats: 528/528 pass (was 521/528)
- shellcheck --severity=warning, all shell + .bats files: clean
- pre-commit run --all-files: all 11 hooks pass
- PSScriptAnalyzer on install.ps1: no new findings vs origin/main; the
  PSAvoidUsingInvokeExpression finding on main is gone with the piped installer
- pwsh: install.ps1 parses, -Help and -DryRun run, Compare-MsbVersion correct on 10
  cases, Get-MsbVersion fails closed on nonzero exit and on a stale $LASTEXITCODE,
  the checksum regex matches the real published manifest and rejects decoys, and
  Expand-Archive handles the real Windows bundle (it is a zip: msb.exe + libkrunfw.dll)
- ./scripts/verify-msb-pin: 29/29 live against real 0.6.18/0.7.2/0.7.3 binaries

Windows remains a preview path, still unexercised on a real Windows host.

Refs: #510

* fix(install): exempt dry runs from the active-msb verification

`--dry-run` installs nothing, so verifying the active msb afterwards checks a
state the run deliberately did not create. On a host with no msb, a plain
`sh install.sh --dry-run --yes` therefore printed every step as OK and then
died on the final check:

    error: msb was installed, but no msb is active on PATH.

That is a false alarm on the exact command a cautious user runs first, and it
exits non-zero, so it also fails any script that smoke-tests the installer.

install.ps1's Assert-MsbSupported has always had this guard (`if ($DryRun) { return }`);
the shell side did not. Found while writing reviewer instructions for the PR --
the first command those instructions suggest is the one that failed.

Every other dry-run test in the suite passes --no-msb, which is why none of them
caught it. The new regression test deliberately does not, and also asserts that a
dry run writes nothing.

Refs: #510

* test(install): cover the failed-msb-download degrade path

Reviewer finding (F2, second half): a FAILED msb download used to die in
verify_active_msb_supported before reaching the 'Skipping msb' degrade, turning
a successful acq install into a total-failure exit -- and no test stubbed a
failing curl, so nothing caught it. The gate is already conditional on
install_msb_pinned succeeding (fixed earlier in this branch); this adds the
missing coverage: a curl that fails the bundle download must leave acq installed,
print 'Skipping msb', write no msb, and exit 0.

Refs: #510

* fix(msb): fail closed on non-final versions

* fix(msb): align installer version finality checks

* test(install): make pwsh version parser test portable

* fix(msb): reject failed version probes
mogul added a commit that referenced this pull request Oct 6, 2026
🤖 I have created a release *beep* *boop*
---


##
[4.0.0](v3.1.0...v4.0.0)
(2026-10-06)


### ⚠ BREAKING CHANGES

* **kits:** acq no longer installs podman/OCI tooling by default. Users who need docker, docker compose, or rootless podman when the base image doesn't provide it must opt into the oci-engine kit with `acq configure`, `--kit`, or `ACQ_EXTRA_KITS`.
* **kits:** acq no longer performs native OpenCode npm fallback installation. OpenCode is supplied by the pinned opencode agent kit from agentic-coding-patterns v1.12.0, currently delivering OpenCode v2.0.22. If the kit is unavailable or invalid, acq fails closed instead of installing opencode-ai from npm.
* **kits:** existing acq sandboxes with adapter-installed podman are not migrated in place. Recreate affected sandboxes or add the oci-engine kit before relying on OCI behavior after stop/start.

### Features

* **acq:** choose the host port per sandbox, and let --publish pin it
([#526](#526))
([410f332](410f332))
* **acq:** export ACQ_WORKSPACE and ACQ_CLONE into the guest on both
backends
([#457](#457))
([e0b9a3b](e0b9a3b))
* **acq:** interactive `acq configure` UI for extra kits + token-scoping
defaults
([#500](#500))
([13d9d3a](13d9d3a))
* **kits:** adopt pinned agent and capability kits
([#474](#474))
([6a67063](6a67063))
* **secrets:** add Windows-native DPAPI secret backend
([#464](#464))
([907f404](907f404))
* **windows:** add acq preview install path
([#463](#463))
([d6d76f4](d6d76f4))


### Bug Fixes

* **acq:** use sandbox workspace for github scoping
([#476](#476))
([1610835](1610835))
* **ci:** bump js-yaml to 4.3.2 to clear a high-severity CPU-DoS
advisory
([#454](#454))
([3217b49](3217b49))
* **ci:** bump smol-toml to 1.7.1 to clear an infinite-loop DoS advisory
([#459](#459))
([c2291bf](c2291bf))
* **ci:** centralize markdownlint coverage
([#548](#548))
([a27c31e](a27c31e))
* **ci:** pin markdownlint-cli2 once, clear GHSA-253c, unify file
selection
([#527](#527))
([e712685](e712685))
* **ci:** unblock unrelated PRs from the bash32 required-check deadlock
([#513](#513))
([187aa57](187aa57))
* **install:** keep curl | sh stdin intact and tap msb formula first
([#462](#462))
([c808d74](c808d74))
* **install:** trust the microsandbox-acq dependency before brew install
([#534](#534))
([ffac0fa](ffac0fa))
* **kits:** land collapsed kit stack implementation
([#549](#549))
([bcd5b9c](bcd5b9c))
* **msb:** allow Playwright browser downloads
([#477](#477))
([0c399c5](0c399c5))
* **msb:** block unsafe 0.7 migration versions
([#510](#510))
([3634046](3634046))
* **msb:** carry every origin URL, keep the scratch private, fail closed
on a copy miss
([#488](#488))
([babcd49](babcd49))
* **msb:** carry the source checkout's git identity into the --clone
scratch
([#443](#443))
([320d784](320d784))
* **msb:** follow the host resolvers and disable DNS rebind protection
in the guest
([#444](#444))
([d2f4bdc](d2f4bdc))
* **msb:** point the --clone scratch's origin at the source checkout's
remote
([#455](#455))
([7f48b94](7f48b94))
* **msb:** refresh ssh authorization for port publish
([#446](#446))
([72a6bd5](72a6bd5))
* **msb:** session parity for exec/attach/shell (cwd, terminal identity,
login shell)
([#427](#427))
([6261eda](6261eda))
* **msb:** skip the recursive home chown when the image baked the agent
user
([#432](#432))
([d8f66a4](d8f66a4))
* **msb:** thread the merged kit env onto kit lifecycle commands
([#519](#519))
([2248a92](2248a92))
* **msb:** translate kit wildcard network rules
([#451](#451))
([e22bbc3](e22bbc3))
* **msb:** verify post-hoc port forward listener
([#530](#530))
([8ca50e1](8ca50e1))
* **msb:** warn on broadened kit wildcard rules
([#460](#460))
([6ded3ce](6ded3ce))
* **msb:** warn when ssh-agent route is missing
([#448](#448))
([3a767a4](3a767a4))
* **sbx:** emit valid setup.install shape for the git-identity mixin
([#492](#492))
([8e6160c](8e6160c))
* **sbx:** raise the version floor to 0.39.0, where sbx create --env
first exists
([#491](#491))
([30f9ecf](30f9ecf))
* **sbx:** wait for startup barrier before attach
([#512](#512))
([33c83c5](33c83c5))
* **secret:** reject multiline test secret values
([#447](#447))
([f2da950](f2da950))
* **secrets:** propagate rotation across backends
([#509](#509))
([9641958](9641958))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Bret Mogilefsky <bret.mogilefsky@gsa.gov>
wz-gsa pushed a commit to GSA-TTS/agentic-coding-patterns that referenced this pull request Oct 10, 2026
…#441)

## Summary

Adds `integrations/isolation/docs/scope-layers.md`: how global, team,
and personal `acq` kits stack through `ACQ_EXTRA_KITS`, a per-field
composition table, what belongs in each layer, authoring gotchas, one
list of backend differences, and the parser rules to check against your
`acq`.
Adds the copy-and-rename template `acq-kits/examples/team-kit/`: a valid
hybrid/v1 mixin with one live, harmless value per extension point, a
host-side `env.sh`, a sample ADR, README, TROUBLESHOOTING, and a
`scripts/verify` that asserts the composition live.
Second of three stacked PRs for #421:
#476, then this one, then the
`personal-kit` template.

**Reviewer ask: correct the composition table if any field composes
differently than described**, especially the rows marked "sbx: composed
natively". It was checked against acq v4.0.1
(GSA-TTS/agentic-coding-quickstart `1679530`), which includes the
`environment` and `publishedPorts[]` fixes
(GSA-TTS/agentic-coding-quickstart#519,
GSA-TTS/agentic-coding-quickstart#526) and adds the pinned OpenCode
agent kit between the global and team layers. The `volumes[]` and
`commands[]` rows describe current behavior while the stacked-kit
conflict rule is decided in GSA-TTS/agentic-coding-quickstart#533, which
names this PR as its trigger.

## Eligibility & Accountability

- [x] I am a **federal employee or contractor** (PRs merge with
`@GSA-TTS/agentic-coding-team` code-owner approval; eligibility is
attested here, not authenticated). *Anyone may open issues without
this.*
- [x] **I understand this change and can explain it in my own words** —
I am the author of record and accountable for it.
- [x] By opening this PR, I **attest I have the right to release this
work** under the repository's [CC0-1.0](../LICENSE) dedication.

## AI Assistance Disclosure

- [x] AI tools were used in this contribution.
- [x] I verified all AI-generated content: no fabricated/hallucinated
APIs, dependencies, citations, or placeholder content; any AI-suggested
dependency was confirmed to exist.
- [x] Any inherited/reused third-party material retains its original
license and is identified (not represented as public domain).

## Type of Change

- [x] New tool/editor integration (`integrations/`)
- [x] Documentation update

## Motivation

Every team adopting `acq` builds the same team layer between the global
kits and each repo's agent config, then a personal layer on top.
`ACQ_EXTRA_KITS` supports this, but nothing here said how stacked kits
compose, what belongs in each layer, or gave a validated starting point.
login.gov Team Data runs such a kit in production; this generalizes what
that team learned.

Parser and adapter defects the doc works around, stated once under
"Check against your acq" and filed upstream:

- a trailing `# comment` is kept in top-level fields and in
`commands[]`, where it skips the command or changes an argument
(GSA-TTS/agentic-coding-quickstart#558);
- `environment` values are cut at the first `#`, even inside quotes
(GSA-TTS/agentic-coding-quickstart#559);
- `acq kit validate` passes commands the translator skips or empties
(GSA-TTS/agentic-coding-quickstart#563);
- flow-style argv parses as an empty command
(GSA-TTS/agentic-coding-quickstart#560);
- on msb an empty file fails its drop, and a failed drop stops that
kit's `environment` and `commands[]`
(GSA-TTS/agentic-coding-quickstart#561).

OpenCode 2.x never loads `instructions` files (anomalyco/opencode#51341,
fix in anomalyco/opencode#51422, tracked in
#482), and the template delivers team
conventions that way. Which OpenCode runs depends on the image: the
agent kit installs 2.0.22, but an image that bakes 1.x puts it first on
`PATH`. Checked live on msb 0.7.7 with acq v4.0.1: acq's own template
image for `acq run opencode` (`sandbox-templates:opencode-docker`) runs
1.18.32 and `devenv-opencode:1.0.0` runs 1.18.23, so the conventions
load; `sandbox-templates:shell-docker` has no OpenCode of its own and
runs only 2.0.22, so they are ignored. `scripts/verify` now fails when
2.x is what runs, and did on `shell-docker`. The doc states this in
prose, and the permission sketch follows the global kit's default-allow
posture (ask before posting, deny merge/approve, pipeline runs, CI
variables, and API writes).

Not shipped: an optional `tui.jsonc` with `OPENCODE_TUI_CONFIG`. TUI
settings are mostly personal, a personal kit would replace the team file
whole, and `verify` could assert only that the variable and file exist.

## Testing

- [x] Ran `make validate` locally (all checks pass)
- [x] Ran `make test` (if applicable): 448 passed;
`scripts/tests/test_validate_kits_*.py`: 95 passed
- [x] Tested with at least one real scenario:
`examples/team-kit/scripts/verify` on msb 0.7.7 with acq v4.0.1: 20
passed, 0 failed, 1 skipped (the empty TEAM-SPECIFIC section). Not
re-run on sbx for this revision (the host's sbx session needed `sbx
login`); the doc no longer claims sbx observations.
- [x] Examples are concrete and realistic
- [x] Commit messages follow [Conventional
Commits](https://www.conventionalcommits.org/) format

Also run: `validate-kits.py --strict`, `acq kit validate` on the
template, `ruff check scripts/`, `ruff format --check scripts/`, `make
generate-check`, markdownlint, `shellcheck -s sh env.sh`, and `env.sh`
under sh, bash, zsh, and dash.

## Safety Checklist

- [x] No secrets, API keys, tokens, or passwords included
- [x] No PII (names, emails, SSNs, addresses)
- [x] No CUI (Controlled Unclassified Information)
- [x] No internal URLs, hostnames, or system paths (the reference
implementation is named, not linked; examples use
`example.gov`/`example.org`)
- [x] No customer data or proprietary information
- [x] Pattern defines `prohibited_content` in output contract (if
applicable): not applicable, no SKILL.md pattern

## Quality Checklist

- [x] Plain language used (Grade 10 or below preferred)
- [x] Examples included
- [x] Prerequisites clearly stated

## Validation Results

```
make validate
✓ All validators passed
```

## Related Issues

Relates to #421 (followed by the
`personal-kit` template)
Depends on #476 (placement B)
Relates to GSA-TTS/agentic-coding-quickstart#533 (stacked-kit merge
rules for volumes and startup steps)
Relates to GSA-TTS/agentic-coding-quickstart#517 (symlinked `files[]`
sources on msb)
Relates to GSA-TTS/agentic-coding-quickstart#482 (acq's `~/.rc.d` login
hook; the personal-kit row relies on it)
Relates to #482 (OpenCode 2.x ignores
`instructions`)
Relates to GSA-TTS/agentic-coding-quickstart#557 (`acq configure` and
team kits), GSA-TTS/agentic-coding-quickstart#558,
GSA-TTS/agentic-coding-quickstart#559,
GSA-TTS/agentic-coding-quickstart#560,
GSA-TTS/agentic-coding-quickstart#561,
GSA-TTS/agentic-coding-quickstart#562,
GSA-TTS/agentic-coding-quickstart#563 (filed from this work)
Relates to GSA-TTS/agentic-coding-quickstart#500,
GSA-TTS/agentic-coding-quickstart#509,
GSA-TTS/agentic-coding-quickstart#519,
GSA-TTS/agentic-coding-quickstart#526

## Additional Context

- `scripts/verify` cannot run in this repo's CI (no nested sandboxes);
the offline gate is `validate-kits.py`.
- The template README links the pattern doc through the repository root
with the file path as text: a copied kit cannot follow relative links,
and `blob/main/…` links to files added here would fail link-check until
merge.

## Reviewer Notes

**Reviewers should verify:**

- [ ] The composition table matches acq's behavior on msb, and the rows
marked "sbx: composed natively" describe sbx correctly
- [ ] Backend differences and Check against your acq match what you see
on your acq
- [ ] Safety checklist items confirmed
- [ ] `make validate` passes in CI
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.

feat(acq): interactive acq configure UI for extra kits + token-scoping defaults

3 participants