Commit 370e6b6
feat(orchestrator): forward per-item capability selection in DispatchExecutor.fire (#13a) (#142)
`DispatchWork.capabilities` and `.addCapabilities` have been in the core contract
since at least 0.4.0 — `dispatch.d.ts` is byte-identical between 0.4.0 and 0.5.0 —
but `fire()` forwarded eleven fields and neither of these, so the selection was
unreachable from a plan.
The consequence is not cosmetic. Without a per-item override, changing a worker's
workspace substrate requires re-registering the subagent, mutating a registration
shared by every plan that names it, so two runs against different workspace
snapshots cannot coexist. Sharper: `resolveLatest` resolves per DISPATCH, not per
run, so a re-registration mid-run can change worker identity between two items of
the same run — and nothing afterwards reports it, because each manifest is
internally consistent and the bundle still verifies intact.
Follows the shape-guard posture its two neighbouring carriers already use: a
non-array value is IGNORED rather than thrown, so one malformed field cannot fail
an item. Spread conditionally for a reason specific to this field — a
present-but-undefined `capabilities` reads as "replace the bound set with nothing"
rather than "not specified", which would be a silent capability strip.
TWO THINGS THE ISSUE ASKED FOR THAT TURNED OUT TO BE UNNECESSARY, both verified
rather than assumed:
1. "Accept the field in validateRun" is a no-op. `validateRun` never references
`inputs` at all (grep count 0); `WorkItem.inputs` is `Record<string, unknown>`
and nothing constrains its keys.
2. Shape validation does not strip it. `tick.ts:180` runs
`shape.inputSchema.safeParse(fireItem.inputs)` but checks only `.success` and
discards `parsed.data`, so the unstripped `fireItem.inputs` is what reaches the
executor. Had it used `parsed.data`, a plain `z.object` would have silently
dropped the new key and the forward would have been a no-op for every
shape-validated item.
The audit trail needed no change: `DispatchExecutorManifest.capabilities` already
records the resolved {name, contentHash} set per dispatch, sealed via `canonEntry`.
Verified the omit-when-absent test BITES: making the spread unconditional makes it
fail. It passes before the fix too, so without that check it would have been
indistinguishable from a vacuous test.
818 orchestrator tests pass (103 files), `pnpm -r lint` and `pnpm -r typecheck`
clean.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 3a807e5 commit 370e6b6
2 files changed
Lines changed: 97 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
76 | 92 | | |
77 | 93 | | |
78 | 94 | | |
| |||
96 | 112 | | |
97 | 113 | | |
98 | 114 | | |
| 115 | + | |
| 116 | + | |
99 | 117 | | |
100 | 118 | | |
101 | 119 | | |
| |||
Lines changed: 79 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1895 | 1895 | | |
1896 | 1896 | | |
1897 | 1897 | | |
| 1898 | + | |
| 1899 | + | |
| 1900 | + | |
| 1901 | + | |
| 1902 | + | |
| 1903 | + | |
| 1904 | + | |
| 1905 | + | |
| 1906 | + | |
| 1907 | + | |
| 1908 | + | |
| 1909 | + | |
| 1910 | + | |
| 1911 | + | |
| 1912 | + | |
| 1913 | + | |
| 1914 | + | |
| 1915 | + | |
| 1916 | + | |
| 1917 | + | |
| 1918 | + | |
| 1919 | + | |
| 1920 | + | |
| 1921 | + | |
| 1922 | + | |
| 1923 | + | |
| 1924 | + | |
| 1925 | + | |
| 1926 | + | |
| 1927 | + | |
| 1928 | + | |
| 1929 | + | |
| 1930 | + | |
| 1931 | + | |
| 1932 | + | |
| 1933 | + | |
| 1934 | + | |
| 1935 | + | |
| 1936 | + | |
| 1937 | + | |
| 1938 | + | |
| 1939 | + | |
| 1940 | + | |
| 1941 | + | |
| 1942 | + | |
| 1943 | + | |
| 1944 | + | |
| 1945 | + | |
| 1946 | + | |
| 1947 | + | |
| 1948 | + | |
| 1949 | + | |
| 1950 | + | |
| 1951 | + | |
| 1952 | + | |
| 1953 | + | |
| 1954 | + | |
| 1955 | + | |
| 1956 | + | |
| 1957 | + | |
| 1958 | + | |
| 1959 | + | |
| 1960 | + | |
| 1961 | + | |
| 1962 | + | |
| 1963 | + | |
| 1964 | + | |
| 1965 | + | |
| 1966 | + | |
| 1967 | + | |
| 1968 | + | |
| 1969 | + | |
| 1970 | + | |
| 1971 | + | |
| 1972 | + | |
| 1973 | + | |
| 1974 | + | |
| 1975 | + | |
| 1976 | + | |
1898 | 1977 | | |
0 commit comments