Repository navigation
feat(acq): interactive acq configure UI for extra kits + token-scoping defaults - #500
Conversation
…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
Demo:
|
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
Updated demo: built-in kits now shown as frozen rowsPer review feedback, the built-in kits are no longer listed in a separate banner The clip shows the four frozen built-ins, then the arrow-key/space multiselect Recorded from a real PTY-driven |
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
Fixed: picker no longer duplicates the top rowGood catch — that was a real rendering bug, not just a recording glitch. Rows Fix: each row (and the header) is now fit to the terminal width before Regression test added ( |
|
AI-assisted review (OpenCode), advisory — needs a human to confirm before it drives a change. Reviewed at head 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 Blocker: duplicate ADR numberThis PR adds The branch point explains it — the merge-base is Should-fix:
|
|
Addressed the review feedback in commit 3d166a0 and ready for re-review. Changes made:
Additional tests added:
Verification:
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
left a comment
There was a problem hiding this comment.
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.
-
Major: configured extra kits are not applied on
sbxfresh creates/runs.acq:944,acq:1072,acq.backends/sbx.sh:260
A user can selectopenchamberinacq configure, then runACQ_BACKEND=sbx acq create opencode ., but the selected kit never reachessbx create.acq_resolve_backendbuildsKITSbefore dispatch;_acq_apply_configured_extra_kitsandcreate_time_kit_pickermutateACQ_EXTRA_KITSlater, but nothing rebuildsKITSbeforesbxemits--kitflags fromfor k in "${KITS[@]}".msbreads currentACQ_EXTRA_KITSdirectly during provision, so the bug is backend-specific. This also explains why the new tests can pass: they assert the helper mutatesACQ_EXTRA_KITS, but do not assert a positive configured-extrasbx create --kit ...openchamber...call. -
Minor: the C1 sanitizer corrupts valid UTF-8 text.
acq.backends/prompt.sh:56
The fix strips raw bytes0x80-0x9funderLC_ALL=C. Those byte values are also valid UTF-8 continuation bytes, so normal text likedon’t,…, ornaïveis rendered as invalid/mangled UTF-8. I reproduced the byte-level behavior with the sametr -d '\000-\037\177\200-\237'range. The C1 escape concern is real, but this implementation damages legitimate non-ASCII labels/descriptions. -
Minor:
config.yamlstill 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 UIis parsed asopenchamber # browser UI, causing fail-closed unknown-kit errors on#,browser, andUI;extra_kits: "openchamber paseo"similarly fails on quoted tokens. Since this file is namedconfig.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
|
Addressed the latest review feedback in 3e495ef and brought the branch up to date with main. Changes made:
Adversarial review:
Verification:
Ready for re-review. AI-assisted (OpenCode). |
basiliskus
left a comment
There was a problem hiding this comment.
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:
- Configured extra kits never reached the backend — resolved. Reproduced fixed on both sbx and msb, per above. Previously this was
0on both, while env-suppliedACQ_EXTRA_KITSand--kitboth worked — i.e. only the configured path was inert, which was the feature's whole point. - Sanitizer corrupted valid UTF-8 — resolved.
_acq_prompt_sanitizenow round-trips throughiconv -f UTF-8 -t UTF-8 -cand strips UTF-8-encoded C1 (C2 80-9F) rather than deleting raw bytes in0x80-0x9f.don’t café naïve …survives intact; raw ESC, raw C1, UTF-8 C1 and DEL are still removed. The earlierdon’t→don\342tcorruption is gone. - 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-118covers the three forms. One edge remains, below. COLUMNS<20replaced with 80 — not resolved. Still a nit, still not worth blocking.
Below the bar:
acq.backends/common.sh:1167-1183strips inline comments before unwrapping quotes, so a valid YAML scalar likeextra_kits: "openchamber # browser UI"is truncated to"openchamberand then fails closed on an unknown kit token. Only reachable by hand-editing, and failing closed is the safe direction — but the file is namedconfig.yamland 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_colsreturns 80 for anyCOLUMNSunder 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:
mainhas0028/0029, this branch addsdocs/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
|
Addressed the quoted-config review point. The flat config reader now preserves Verification: Given the small blast-radius of the change I'm going to exercise Maintainer's Right to skip getting another review round done. |
* 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
🤖 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>
…#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



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 labelsescape-sanitized (as in
progress.sh). HonorsACQ_NO_PROMPTand anACQ_PROMPT_TEST_INPUTtest hook. No new pinned dependency — consistentwith the repo's "one clone, no extra deps" posture.
acq configure(new command) — shows the current config, runs the opt-inkit multiselect, then a confirm for the token-scoping default, and persists to
~/.config/acq/config.yaml. Auto-offered once on first run.backend:awk parser is generalized to aflat-key reader/writer (
_acq_config_read_field/_acq_config_write_field);adds
extra_kits:andscope_github_token:while preservingbackend:.openchamber,paseo, sourced from the already-pinnedpatterns bundle (
PATTERNS_KIT_REF/PATTERNS_KIT_DIR). The four built-in kitsstay mandatory (shown as informational, not toggleable).
prime-agentisintentionally omitted — it is a skeleton at the current pin.
acq create/run— pre-populates the picker from the global defaults andfolds the selection into
ACQ_EXTRA_KITS, persisted per-sandbox via theexisting kit-record path (ADR-0017) and reloaded on resume. An exported
ACQ_EXTRA_KITSstill wins (env > config).prompt; ADR-0013's fine-grained-PAT minting is unchanged.
docs/CONCEPTS.md,docs/howto/acq.md,CONTRIBUTING.md.Design / scope decisions
token default (per issue discussion).
config.yaml; per-sandbox deviations reuse the existingper-sandbox kit records.
.acq/proposal infeat(acq): per-repo .acq/ config for kits and dev image (one place, not per-invocation) #232 — this is user-global config.
Verification
./scripts/test-acq-bats— 493/493 pass (addstest/bats/135-configure.bats, 19 cases: config round-trip + key preservationACQ_PROMPT_TEST_INPUT,acq configuredispatch/--help/non-interactivefail-open, env-wins precedence).
90-sbx-startup-kitplaybook-probe test: it is a pre-existingissue on
main(it fails whenever the runner's real$HOMEcarries a globalgit identity —
~/.gitconfigwithuser.name/user.email— leaking into thestub). It is already fixed by the open PR
fix(acq): use sandbox workspace for github scoping #476, which adds
HOME/XDG_CONFIG_HOMEisolation 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 alsomentioned
70-msb-backend; that was a local-environment false alarm (a stray~/.config/acq/config.yamlfrom ad-hoc testing), not a real failure — feat(bootstrap): add init-project script for project bootstrapping #70passes on
mainand on this branch.backend unsetchange here also removes a now-emptyconfig.yamlso acleared config looks pristine (and first-run detection still fires).
--severity=warning, matching.shellcheckrc): clean onacq,acq.backends/prompt.sh,acq.backends/common.sh.bash -nsyntax 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-interactiveruns behave exactly as before (fail-open to defaults).
Security impact
per-sandbox path (AC-6, IA-5, ADR-0013 unchanged).