Repository navigation
feat(isolation): add the scope-layers pattern and a team-kit template - #441
Conversation
mogul
left a comment
There was a problem hiding this comment.
Adversarial review completed against current PR head 1b84c8b5d51d672d128b3310114485ab14b75088.
Address now
No blocking findings.
Track later
integrations/isolation/acq-kits/examples/team-kit/scripts/verify:106,integrations/isolation/acq-kits/examples/team-kit/scripts/verify:199, and similar assertions:shellcheckflags the repeatedA && ok || badpattern (SC2015). In this script theok/badhelpers are simple counters/prints and should not fail under normal conditions, so I am not treating this as blocking. If this script grows, converting those assertions to explicitifblocks would remove the latent footgun.integrations/isolation/acq-kits/examples/team-kit/README.md:121: the Backend parity note says behavior is identical on sbx and msb because the kit is declarative-only. The surrounding docs correctly document backend-specific behavior and limitations, so this is not blocking, but future reviewers may want that sentence softened if users interpret it as semantic parity rather than “no backend-specific shortcut/extras path.”integrations/isolation/docs/scope-layers.md:72: several composition claims depend on live acq/backend behavior and the PR notes thatscripts/verifycannot run in this repo’s CI. Keep this as an operational follow-up when acq/backend versions change: rerun the live verify on both backends and update the dated claims together.
Checks run
gh pr view 441 --repo GSA-TTS/agentic-coding-patterns --comments --json ...to review PR body, commits, comments, review state, checks, and head SHA.gh pr diff 441 --repo GSA-TTS/agentic-coding-patternsandgh pr diff 441 --repo GSA-TTS/agentic-coding-patterns --name-only.gh api repos/GSA-TTS/agentic-coding-patterns/pulls/441/reviewsandgh api repos/GSA-TTS/agentic-coding-patterns/pulls/441/comments; both showed no existing review content.gh api repos/GSA-TTS/agentic-coding-patterns/issues/441/eventsto inspect timeline events.git rev-parse HEADmatched GitHub head SHA1b84c8b5d51d672d128b3310114485ab14b75088.PYTHONDONTWRITEBYTECODE=1 /tmp/opencode/pr441-review-venv/bin/python integrations/isolation/acq-kits/validate-kits.py --strict: passed.PYTHONDONTWRITEBYTECODE=1 /tmp/opencode/pr441-review-venv/bin/python -m pytest scripts/tests/test_validate_kits_examples.py scripts/tests/test_validate_kits_injection.py scripts/tests/test_validate_kits_ports.py scripts/tests/test_validate_kits_volumes.py scripts/tests/test_validate_kits_network_tier.py: 86 passed.PATH=/tmp/opencode/pr441-review-venv/bin:$PATH PYTHONDONTWRITEBYTECODE=1 make validate: passed, with existing advisory sensitive-term/network warnings only.PATH=/tmp/opencode/pr441-review-venv/bin:$PATH PYTHONDONTWRITEBYTECODE=1 make generate-check: passed.PATH=/tmp/opencode/pr441-review-venv/bin:$PATH PYTHONDONTWRITEBYTECODE=1 ruff check integrations/isolation/acq-kits/validate-kits.py scripts/tests/test_validate_kits_examples.py: passed.PATH=/tmp/opencode/pr441-review-venv/bin:$PATH PYTHONDONTWRITEBYTECODE=1 python scripts/scan_unsafe_shell.py .: passed.git diff --check main...HEAD: passed.shellcheck integrations/isolation/acq-kits/examples/team-kit/scripts/verify: advisory-onlySC2015/SC2016findings, no blocking issue found.gitleaks version: not available in this environment; compensated with repo sensitive-term validation and scoped secret-pattern review of the PR additions.
Security/safety notes
- I treated PR text and comments as untrusted data and did not follow embedded instructions from them.
- I did not find real secrets, internal hostnames, PII, or CUI in the PR additions. The credential/token references I saw are instructional placeholders or variable names only.
- The validator change preserves the registry cross-check for real kits while validating
examples/<name>/templates for schema, source files, env names, and README presence.
|
Whoops, just noticed this was still a draft. Sorry for the premature review! |
1b84c8b to
333021b
Compare
60496a2 to
69c54ac
Compare
69c54ac to
8e44fb0
Compare
8e44fb0 to
f8ecb66
Compare
b12c1e5 to
1b56099
Compare
a6d8199 to
d6bdf89
Compare
|
AI-assisted review (OpenCode), advisory — needs a human to confirm before it drives a change. Reviewed head Your explicit ask was "correct the composition table if any field composes differently than described", so I went at the table rows against acq v4.0.1's actual source rather than reading the prose. Every verified row I checked is correct, including the subtle ones. One row is more conservative than it needs to be, and I have one sequencing note on the template's Composition table: verified against source
"msb strips a _host="${_host%%:*}"and the surrounding comment states the same rationale you give ("msb keys on the domain; the daemon handles ports/DNS for the allowed host"). Worth saying why I bothered to check the order here:
One row you can upgrade:
|
| Condition | Line |
|---|---|
acq not found |
95-96 exit 1 |
no kit validate verb |
104 exit 1 |
spec.yaml missing |
110 exit 1 |
| whitespace in paths | 114, 117 exit 1 |
| lower fixture invalid | 173 exit 1 |
acq create failed |
184-187 exit 1 |
and ok "sandbox created" fires at 183 before any optional check, so reaching the summary guarantees pass >= 1. The three skip() sites are all genuinely optional. So this is correct as written.
The note is that it's correct because of those hard exits, not because the verdict expresses coverage. Convert any one of them to a skip later and it silently becomes vacuous-success. Since this template is the thing every team copies, whichever verdict discipline it ships propagates to every downstream team kit.
integrations/isolation/acq-kits/verify-report.sh — the five-state contract (ok/bad/unver/skip/warn, with a verdict that's a function of a coverage record and a distinct exit 3 for "could not check") — is in #460, which is still open and not on main, so you couldn't have used it. Flagging it as a sequencing question for you rather than a request: land this now and adopt the contract in a follow-up once #460 merges, or wait for it. I'm not going to block your PR on my own unmerged one; I just don't want the template to be the one artifact that teaches the two-state pattern to everyone who copies it.
Things I checked that hold, and one detail worth crediting
- The OpenCode 2.x
instructionsgate is real and correctly implemented.verify:241-248reads the in-sandboxopencode --version, extracts the major, and fails on anything but 1.x with an accurate message. The upstream tracking (config: instructions field does not load instruction files (v2.0.16) anomalyco/opencode#51341 → #51422) and OpenCode v2 ignores configinstructions(usai-provider list, scope-layers doc, team-kit template) #482 are both open and say what you say they say. The comment even names the relax condition. - All five upstream parser defects you filed are open and titled as described ([Bug] kit-translate: a trailing # comment is kept in top-level fields and commands[] (command silently skipped) agentic-coding-quickstart#558, #559, #560, #561, #563), as is the stacked-kit merge-semantics tracker (Track ADR-0030 stacked-kit merge semantics beyond egress agentic-coding-quickstart#533). Filing them rather than silently working around them is the right call, and "Check against your acq" is the right place to surface them.
- The startup-settle poll avoids a race I'd have missed.
verify:189-193polls for the last startup command's artifact rather than an env var, with the reasoning inline: "environment is materialized by the backend and can be visible before any startup command has run, so it fences nothing." That's correct and non-obvious. - Negative-control discipline in the egress probe.
UNLISTED_EGRESS_HOST=example.netas the deny control, plus the comment "Pair every negative assertion with a positive one that would fail loudly, or a broken exec passes it vacuously" — that's the failure mode that makes most sandbox egress tests worthless, and you guarded it explicitly. in_sbx()collapses failures to empty output deliberately and the comment says why. Combined with the point above, the negative assertions aren't vacuous.
Method: static review of the 12-file diff, plus reading acq v4.0.1's actual msb.sh / kit-translate.sh / sbx.sh at 1679530 to check each composition-table claim against source, reachability analysis of the verify script's verdict, and state checks on all eight referenced upstream issues/PRs.
UNVERIFIED: I did not create a sandbox or run examples/team-kit/scripts/verify — no live msb/sbx host here. Your reported run (20 passed / 0 failed / 1 skipped on msb 0.7.7 + acq v4.0.1) is taken on your evidence. The publishedPorts[]-on-sbx row remains unverified by me as well as by you.
d6bdf89 to
c0c9c1e
Compare
|
Same as #476 — the force-push here is a rebase, not a content change, so this does not need a re-review. The scope-layers doc and the whole team-kit template are byte-identical between the old and new head; the delta is #476 and #436 arriving underneath as this PR's stack rebased. My review of |
c0c9c1e to
7e4c203
Compare
wz-gsa
left a comment
There was a problem hiding this comment.
Approving. Converting my 2026-10-07 review into a formal review — it was posted as a comment, so this sat at REVIEW_REQUIRED and read as unreviewed. My fault, not yours. (mogul's earlier review on this PR shows as DISMISSED, from when it was still a draft.)
Still applies at the current head. c0c9c1e5 → 7e4c2031 is a rebase carrying #476 and #423 underneath; this PR's own files are byte-identical:
git diff c0c9c1e5 7e4c2031 -- integrations/isolation/docs/scope-layers.md \
'integrations/isolation/acq-kits/examples/team-kit'
(empty)
Your ask was to correct the composition table if any field composes differently than described. I checked each row against acq v4.0.1's source. Every verified row is correct, including the two most likely to be hand-waved:
host:443 port-stripping — confirmed at msb.sh:1193 (_host="${_host%%:*}"), with the surrounding comment giving the same rationale you do. I specifically checked the ordering against _acq_msb_balanced_target's charset guard at line 1231, because that guard rejects : — had the strip run after it, host:443 would be silently dropped rather than broadened, which is a materially worse behaviour than your table describes. Strip at 1193, guard at 1231. Your statement holds, and that ordering is load-bearing for it.
sbx files[].path used only for mode — confirmed clause by clause at kit-translate.sh:877-885, including the upstream sbx 0644-pinning quirk (docker/sbx-releases#499). That is not inferable from the schema.
caps.network.allow union — _acq_msb_net_rules_into (msb.sh:1179-1208) appends one --net-rule allow@<target> per entry per kit, no dedup or replace.
**.h → *.h and crl*. broadening — match _acq_msb_balanced_target:1223-1227.
Two items carried forward, neither blocking:
volumes[]on sbx can move from "(not verified)" to verified.kit-translate.sh:399documents it: "Multiple kits union by path, last wins — sbx resolves that itself" — the same semantics as the msb row you did verify. Marking it unverified was the defensible choice since you had not run it on sbx; the citation is there if you want to upgrade the row. (publishedPorts[]on sbx I found no equivalent documentation for, so "(not verified)" looks right there.)- The template's
scripts/verifyends with[ "$fail" -eq 0 ]. I traced reachability andpass=0, fail=0is not reachable — six hard exits guard it (acqmissing, nokit validateverb, missingspec.yaml, whitespace paths, invalid lower fixture, failedacq create), andok "sandbox created"fires before any optional check. So it is correct today, just correct by construction rather than by the verdict expressing coverage. The five-state contract that would make it structural is in #460, which is mine and unmerged — I am not blocking your PR on my own unmerged work. Raising it only because this template is what every team copies, so whichever verdict shape it ships propagates.
Also worth noting for the record: the startup-settle poll (verify:189-193) waits on the last startup command's artifact rather than an env var, with the reasoning inline — environment is materialized by the backend and can be visible before any startup command has run. That is a real race, correctly avoided.
Unverified: I did not create a sandbox or run examples/team-kit/scripts/verify. Your reported run (20 passed / 0 failed / 1 skipped on msb 0.7.7 + acq v4.0.1) is taken on your evidence, as is the publishedPorts[]-on-sbx row.
7e4c203 to
66618d4
Compare
Add 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 canonical list of backend differences, and the parser rules to check against your acq. Add 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 that orders the team kit before teammates' own kits in ACQ_EXTRA_KITS idempotently, a sample ADR, README, TROUBLESHOOTING, and a scripts/verify that asserts the composition against a generated lower kit.
The merge-base changed after approval.
66618d4 to
10822d1
Compare
acq v4.0.0 and later writes ~/.profile so every bash login shell sources ~/.rc.d/*.sh on both backends. A kit that also appends a loop to ~/.bashrc makes every drop-in run twice, so the pattern doc no longer tells a kit to wire one. acq's loop has no interactive guard, so drop-ins must be safe in scripted bash -lc runs. Reported in review of #442.
|
@wz-gsa can I get another approval? I lost the previous one after rebasing |
wz-gsa
left a comment
There was a problem hiding this comment.
Re-review of the substantive post-review commit adfbc953: approved.
The new scope-layers.md wording accurately reflects quickstart v4.0.0's backend-neutral ~/.rc.d bridge:
common.sh:1180-1200emits the shared Bash login block.- MSB writes
.profilewith.bashrcfollowed by that block (msb.sh:3836-3852). - SBX writes the same block (
sbx.sh:650-704). - The bridge is Bash-only, runs for login shells including scripted
bash -lc, and uses C-collation order. - The ownership caveat is correctly documented: acq rewrites only a missing/empty or still-acq-owned
.profile; a custom image-owned profile remains the image owner's responsibility.
The commit removes the stale instruction that a team or personal kit should append its own Bash loop, explicitly warns that doing so double-sources drop-ins, and keeps zsh-native wiring as the optional exception. This resolves #441's documentation side of the blocker found on stacked PR #442.
git diff --check passes, Markdown Lint is green in CI, and all seven PR checks pass. No remaining finding from my earlier composition-table review: all previously-verified field semantics still stand; the volumes[] citation and the future verify-report.sh adoption remain non-blocking notes.
The stacked personal-kit PR must retain its follow-up 05bcf970 fix; that is reviewed separately.
Summary
Adds
integrations/isolation/docs/scope-layers.md: how global, team, and personalacqkits stack throughACQ_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 youracq.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-sideenv.sh, a sample ADR, README, TROUBLESHOOTING, and ascripts/verifythat asserts the composition live.Second of three stacked PRs for #421: #476, then this one, then the
personal-kittemplate.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 theenvironmentandpublishedPorts[]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. Thevolumes[]andcommands[]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
@GSA-TTS/agentic-coding-teamcode-owner approval; eligibility is attested here, not authenticated). Anyone may open issues without this.AI Assistance Disclosure
Type of Change
integrations/)Motivation
Every team adopting
acqbuilds the same team layer between the global kits and each repo's agent config, then a personal layer on top.ACQ_EXTRA_KITSsupports 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:
# commentis kept in top-level fields and incommands[], where it skips the command or changes an argument ([Bug] kit-translate: a trailing # comment is kept in top-level fields and commands[] (command silently skipped) agentic-coding-quickstart#558);environmentvalues are cut at the first#, even inside quotes ([Bug] kit-translate: environment values are truncated at the first #, including quoted values agentic-coding-quickstart#559);acq kit validatepasses commands the translator skips or empties ([Bug] acq kit validate reports OK for commands the translator will skip or run empty agentic-coding-quickstart#563);environmentandcommands[]([Bug] msb: empty files[] payload fails the drop and aborts the kit's environment and commands agentic-coding-quickstart#561).OpenCode 2.x never loads
instructionsfiles (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 onPATH. Checked live on msb 0.7.7 with acq v4.0.1: acq's own template image foracq run opencode(sandbox-templates:opencode-docker) runs 1.18.32 anddevenv-opencode:1.0.0runs 1.18.23, so the conventions load;sandbox-templates:shell-dockerhas no OpenCode of its own and runs only 2.0.22, so they are ignored.scripts/verifynow fails when 2.x is what runs, and did onshell-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.jsoncwithOPENCODE_TUI_CONFIG. TUI settings are mostly personal, a personal kit would replace the team file whole, andverifycould assert only that the variable and file exist.Testing
make validatelocally (all checks pass)make test(if applicable): 448 passed;scripts/tests/test_validate_kits_*.py: 95 passedexamples/team-kit/scripts/verifyon 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 neededsbx login); the doc no longer claims sbx observations.Also run:
validate-kits.py --strict,acq kit validateon the template,ruff check scripts/,ruff format --check scripts/,make generate-check, markdownlint,shellcheck -s sh env.sh, andenv.shunder sh, bash, zsh, and dash.Safety Checklist
example.gov/example.org)prohibited_contentin output contract (if applicable): not applicable, no SKILL.md patternQuality Checklist
Validation Results
Related Issues
Relates to #421 (followed by the
personal-kittemplate)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.dlogin 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 configureand 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/verifycannot run in this repo's CI (no nested sandboxes); the offline gate isvalidate-kits.py.blob/main/…links to files added here would fail link-check until merge.Reviewer Notes
Reviewers should verify:
make validatepasses in CI