Repository navigation
v7.198.0
Fragment gate: path-list transport, listing integrity, and job bounds
What
The pr-standards fragment-gate arms now transport their changed/added path
lists to the classifier in a newline-safe framing, the dispatched arm validates
its files listing, and the job is bounded.
Why
Both arms moved path lists as line-delimited text. core.quotePath=false only
stops git from C-quoting non-ASCII bytes: a path containing a quote, backslash,
or control character still arrived C-quoted and matched no path class (the PR
was waved through as "not user-visible"), and a path containing a newline could
not survive the transport at all — while the quality gate's own git diff -z
view classified the same path as user-visible, so two required checks could
disagree about the same change. The dispatched arm also made two independent
files-API listings (a push between them produced split-brain lists), never
checked the listing against the PR's changed_files count (the API truncates
beyond 3000 files), and never re-asserted its head guard after listing. The
pr-standards job also lacked a timeout-minutes, its pull_request checkout
persisted the job token for PR-authored code to read, and the classifier's
stdin read was unbounded.
Change
scripts/check-pending-fragment.ts --print-requirement gains
--paths-format <newline|nul|jsonl> with --changed-file/--added-file
inputs (byte-bounded at 4 MiB; stdin is bounded twice — an input over the
bound exits 2, and one that produces no data within an idle bound exits 2
instead of pinning the check until the job ceiling; file inputs require an
explicit --paths-format so framed bytes can never silently decode through
the legacy newline framing). The pull_request arm feeds git diff -z temp files; the
dispatched arm materializes ONE gh api --paginate ... --jq '.[]' listing,
fails closed when the entry count differs from the PR's changed_files or the
head moved after the guard, and derives both lists as JSON-encoded paths. The
job declares timeout-minutes: 15 and both checkouts set
persist-credentials: false. The legacy newline stdin mode and --added
flags are unchanged (release-and-publish.yml depends on them), and the
recorded workflow hash in docs/ci/required-check-evidence.json is refreshed
in the same change.
Behavior change
A changed path that git C-quotes (or that contains a newline) now reaches the
same verdict in both pr-standards arms as in the quality gate. A truncated
files listing or a PR head that moves during the dispatched validation fails
closed with an explicit error instead of classifying a partial snapshot. An
input stream larger than 4 MiB makes the classifier exit 2 (unusable) instead
of reading unbounded input.
Migration
None. The classifier's legacy invocation shapes keep working; the new
--paths-format mode is additive.
Task-dispatch model failover works for underscore-named agents (issue #2960)
What
Fixed the Task-dispatch model-fallback chain resolver so an installer-seeded,
underscore-named agent (e.g. test_engineer, critic_oversight,
curator_phase, skill_improver, spec_writer) resolves its configured
fallback_models in the default swarm.
The resolver derived the swarm id with a naive first-underscore split, so
test_engineer produced swarm id test instead of the default swarm — the
fallback chain collapsed to empty, and ONE retryable provider failure (HTTP
429/503-class) advanced the route to exhausted. The next chat.message for
that child session then threw MODEL_FALLBACK_EXHAUSTED, killing the whole
in-flight dispatch on a single provider blip instead of failing over.
The swarm id is now derived by length delta from the canonical role (the same
pattern the curator/skill-improver/turbo failover factories already use), so:
- All 14 installer-seeded underscore agent names resolve their seeded
fallback_modelschain in the default swarm; one retryable failure now
advances to the seeded fallback model instead of exhausting the route. - The designed terminal state is unchanged: exhausting the full
primary+fallbacks chain still throwsMODEL_FALLBACK_EXHAUSTED. - Prefixed multi-swarm names (
local_test_engineer) and unprefixed names
(coder) resolve exactly as before, including names whose casing or
separator differs from the roster (Coder,code-reviewer) — those keep
resolving via the default swarm. - Documented behavior change (fail-closed direction): an UNPREFIXED canonical
name whose first token collides with a real swarm id (e.g.test_engineer
with a swarm namedtest) now resolves via the default swarm instead of
accidentally hitting that swarm; use the prefixed name (test_test_engineer)
to address it. A case-variant spelling (Test_Engineer) now also resolves.
Why
Workstream J slot J13 (frontier-audit finding t1-src-agents-2/S1, dual-review
UPHELD HIGH): fresh installs seed DEFAULT_AGENT_CONFIGS — 14 underscore
agent names with non-empty fallback_models — so the broken path needed no
manual configuration to be live.
Bubblewrap capability probe: fix temp cleanup and the false fs=weak verdict
Fixes two defects in the Linux bubblewrap capability probe in src/sandbox/capability-probe.ts, which feed /swarm diagnose output and the require_filesystem sandbox requirement check.
Changes
- Temp cleanup no longer fails (and leaks): the probe removed its temp directory with
fs.rmSyncwithoutrecursive, which throwsERR_FS_EISDIRon Node andEFAULTon Bun for any directory. Every probe therefore left aswarm-bwrap-probe-*(orswarm-seatbelt-probe-*on macOS) directory in the OS temp dir and reported a misleadingprobe temp cleanup failedreason. Cleanup now passes{ recursive: true, force: true }, and the default_internals.removeProbeTempRootapplies the same flags. - Correct filesystem verdict on working bubblewrap hosts: the probe now binds its temp root read-only (
--ro-bind tempRoot tempRoot) before the writableallowedRootbind. Previously bubblewrap created the unbound parent in its writable tmpfs root, so the out-of-scope probe write succeeded inside the sandbox and the probe reportedfilesystem: weakon hosts where isolation works. Such hosts now reportfilesystem: real, sorequire_filesystemchecks that were wrongly failing on them pass. - Tests: the probe test now pins the
--ro-bindargument and its order before--bind, the cleanup flags, and that the non-empty probe directory is really removed.
Declarative SDD provider selection (SWARM_SDD_SOURCE and sdd.source)
What changed
When a repository has both OpenSpec (openspec/) and Spec-Kit (.specify/) sources and no native .swarm/spec.md, readEffectiveSpecSync reports an ambiguity and returns no effective spec. Resolving it previously required the interactive --source flag of /swarm sdd, which gates, hooks and other non-interactive consumers cannot pass. The provider can now be selected declaratively:
sdd.sourcein the plugin config (.opencode/opencode-swarm.json, or the user config at~/.config/opencode/opencode-swarm.json; the project file wins) acceptsopenspec,speckitorswarm. It is a registered config key, validated by the schema, covered by/swarm doctor, and listed in the generated config reference and the shipped JSON Schema.SWARM_SDD_SOURCE(environment variable, case-insensitive) accepts the same values and takes precedence oversdd.source.- Precedence is: a native
.swarm/spec.md(always wins) →--source→SWARM_SDD_SOURCE→sdd.source→ auto-detection. With nothing set, behavior is unchanged. /swarm sdd status,validateandprojectuse the same resolution, so the CLI and the drift/preflight gates no longer disagree.statusreportsSource selection: …when the provider came from the environment or config.
Safety behavior
A selected provider is final, exactly like --source: if it yields no spec (for example SWARM_SDD_SOURCE=openspec in a repo that only has .specify/, or swarm without a .swarm/spec.md), the repo is not silently re-auto-detected. Instead a one-time advisory (visible in /swarm diagnose) names the selection and the repository, because a missing effective spec downgrades the drift gate to advisory and skips requirement-coverage preflight. An unrecognized SWARM_SDD_SOURCE value is likewise reported once and ignored rather than dropped silently. An invalid sdd.source in a config file is a validation error like any other invalid key: the loader reports it (and /swarm doctor names sdd.source), the loader's fail-secure recovery then applies exactly as it does for every other key (with a user-level config present the project config is discarded until the value is fixed), and the SDD resolver additionally buffers its own advisory and falls through to auto-detect. The resolver reads sdd.source directly from the config files (silently; file-level load problems stay with the full loader), so it can still honor sdd.source from any project config the loader discarded (another invalid key, or a refused prototype-pollution payload such as __proto__) — sdd.source is a three-value provider selector, not a security setting.
Migration
No migration required. Nothing changes unless SWARM_SDD_SOURCE or sdd.source is set. Because the environment variable is process-wide, prefer sdd.source in the project config for per-repository pinning and reserve the variable for CI or one-off overrides.
What changed
- PR-review reviewer and critic lanes no longer void themselves on stray rows:
a verdict row whose post-parse arity is illegal for its marker (a Stage-B
task-keyed[REVIEWED]row, an echoed format line) is quarantined into
diagnostics (identifier and arity only) instead of failing the exact-set
contract and discarding every valid sibling row. A legal-arity row naming an
unassigned ID still voids the lane (spoofing floor unchanged), an assigned
item whose only row was quarantined stays unclaimed, and a quarantined row
never becomes a claim. - Reviewer lanes are told at dispatch that the Stage-B "STRUCTURED VERDICT
LINE" mandate does not apply to them (the system prompt is static, so the
suppression is a controller-composed protocol clause plus a conditional
sentence in the reviewer prompt; non-PR reviewers keep the Stage-B contract). - Operator prompts for reviewer/critic lanes are rejected side-effect-free when
they embed[REVIEWED] |/[CRITIC] |verdict-row template text; prose
mentions of the marker word are unaffected. - Critic grammar:
UPHELDis now truthful at LOW and INFO (rank equality with
the reviewer's severity is still enforced, and UPHELD at NONE stays
rejected); a CONFIRMED reviewer row with severity NONE is now rejected with a
field-level error; the contract card states the UPHELD/DOWNGRADED severity
rules; the critic lane's controller block renders each assigned item's
reviewer severity. - PR critic dispatch now requires the
criticagent (its prompt carries a
PR-contract override suspending the PLAN REVIEW structure when the controller
block is present);critic_finding_validatorand the other critic-family
roles are rejected with a message naming the right agent. - Council explorer lanes get a council-specific output contract: no structured
settlement guidance (the settlement tool does not accept council lanes) and a
plain-marker-rows response-format override of the agent's fenced-JSON
default; the suffix is recognized on re-prompt (idempotent). - The swarm-pr-review skill no longer contradicts the controller: Profile A
template fences are content-only with a separately headed Profile B/C row
formats section (full-grammar rows behind an echo-hostile
EXAMPLE — NOT A LIVE ROW:prefix), the "final line"/"planning preamble"
rules are replaced by the rows-anywhere contract, the collection-deadline
wording matches the observer semantics of issue #2381, and the reviewer row
arity is documented as 12-field with legacy 10-field normalization.
Why
Issue #3310 (Workstream K PR 1 of 4): a PR-review lane that obeyed its Stage-B
system prompt, echoed an operator-supplied row template, or agreed with a
LOW/INFO reviewer finding could not produce a settleable artifact — the
exact-set parser voided the whole lane, the critic transport had no truthful
UPHELD row, and the skill text taught rules the controller does not enforce
(#3275 RC1/RC2/RC4).