@sema-agent/core 7.33.0 → 7.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -1
- package/dist/brain/stream-engine.js +4 -3
- package/dist/brain/timeout.d.ts +14 -4
- package/dist/brain/timeout.js +10 -0
- package/dist/core/runner/prepare-wiring-manifest.js +2 -1
- package/dist/core/secret-env.d.ts +13 -4
- package/dist/core/secret-env.js +17 -8
- package/dist/core/task-registry.js +6 -1
- package/dist/core/tool-policy.d.ts +12 -12
- package/dist/core/wiring-manifest.d.ts +27 -0
- package/dist/core/wiring-manifest.js +11 -0
- package/package.json +1 -1
- package/wire-changes/core@7.33.1.json +237 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 7.33.1 — 2026-09-26
|
|
4
|
+
|
|
5
|
+
### The first-byte lane's retry is a construction-time seat — `BrainTimeoutConfig.firstByteRetries?: 0 | 1` (#1128; additive; +1 optional type key; +0 exports, +0 codes) (@server)
|
|
6
|
+
|
|
7
|
+
- **Before.** The lane's budget was a module constant (`FIRST_BYTE_RETRY_BUDGET` = 1) with no seat, and `maxRetries: 0` does not reach it (the lane's budget is its own, by design). A host that keeps a failover ladder of its own — a second gateway it tries when a call fails — waited the first-byte window and then a retry at twice the knob before the failure reached that ladder: 300 s + 600 s at the default, 30 s + 60 s even at an explicit 30 s knob.
|
|
8
|
+
- **Now.** `firstByteRetries: 0 | 1` on every first-party brain's config (absent ⇒ 1, the reference implementation's one retry — unchanged). `0` ⇒ the first window's expiry is the call's terminal: one request, no retry frame, the existing one-window sentence (`[network] first-byte timeout: no response headers within <w1>ms (firstByteTimeoutMs) — …`). Independent of `maxRetries` in both directions, construction only (no per-call advisory twin). A value other than `0` / `1` fails the call before anything is sent, naming the knob (the watchdog knobs' own refusal, `config.stall_timeout_invalid`, at the same resolution point — like theirs, the code stays inside the brain and the caller reads the message) — never folded to the default or to `0`. The derived floors (`firstByteLaneKnobMs`, `WORKFLOW_AGENT_STALL_MS`) read the default and do not move.
|
|
9
|
+
- **Who is hurt.** Nobody: absent is byte-for-byte 7.33.0. **Compensation** n/a.
|
|
10
|
+
- Pins: `test/backlog1128-first-byte-retries.test.ts` (real transport, a local gateway that never writes headers: `0` ⇒ one request and the one-window terminal on all three brains, also under `maxRetries: 10`; explicit `1` and absent ⇒ two requests and one retry frame with `maxRetries: 1` under `maxRetries: 0`; twelve bad values refused by the resolver and through a brain with nothing sent; the derived floors). Docs: `docs/TIMEOUTS.md` (a retry-knob column; "a host with a failover ladder sets `0`"), `docs/sdk/03-brains-and-models.md`. Wire sheet: `docs/wire-consumption/unreleased-1128.md`.
|
|
11
|
+
|
|
12
|
+
### Fix — git's environment config group survives the secret-env scrub whole: `GIT_CONFIG_KEY_<i>` is no longer taken for a credential unless its value carries one (#1129; behavior; +0 exports; `isSecretEnvKey` gains an optional `value` argument; +0 wire keys) (@server)
|
|
13
|
+
|
|
14
|
+
- **Before.** The shape rule reads a bare `KEY` word with a numbered suffix as a credential, so `GIT_CONFIG_KEY_<i>` — git's env config, where the member holds a config entry's NAME (`user.name`, `safe.directory`, `http.extraHeader`) and `GIT_CONFIG_VALUE_<i>` its value — was dropped while `GIT_CONFIG_COUNT` and the value stayed. Every git child of a scrubbed env then refused to start: `error: missing config key GIT_CONFIG_KEY_0` / `fatal: unable to parse command-line config`, exit 128 (measured through the default exec env on a host carrying the group).
|
|
15
|
+
- **Now.** The exemption side of the exact-name arm reads a second table beside the exact names, under the same three admission criteria: NAMED FAMILIES, `<STEM>_<i>` with `i` a canonical decimal — the spelling the reading tool builds itself. One member: `GIT_CONFIG_KEY`. Its row states criterion 2 ("the value is not a credential") as a screen on the VALUE rather than a standing claim, because a config key's subsection can be a URL and git authenticates with a URL's userinfo (`url.https://x-access-token:<token>@github.com/.insteadOf`): a member whose value carries `<scheme>://<userinfo>@` (any character but `/` counts as userinfo, whitespace included) is not exempt and drops by the shape rule exactly as before (finding `suffix-rule`). The screen is the row's own admission condition, read by the one classification (`classifySecretEnvKey(name, value?)`); `scrubSecretEnv` passes each entry's value, and `isSecretEnvKey(name)` without a value reads as in 7.33.0 (a family member is `true` — the name alone cannot clear); `isSecretEnvKey(name, value)` gives the scrub's answer for that entry. Unchanged: the bare `GIT_CONFIG_KEY`, a decorated (`X_GIT_CONFIG_KEY_0`), padded (`GIT_CONFIG_KEY_01`) or suffixed (`GIT_CONFIG_KEY_ID`, `GIT_CONFIG_KEY_0_TOKEN`) name, and every credential name (`GIT_API_TOKEN`, `GIT_PASSWORD`, `GH_TOKEN`) still drop and are still reported through `config.secret_env_scrubbed` / `onSecretEnvScrub`; the credential list still wins a collision.
|
|
16
|
+
- **Who needs it.** Any deployment whose host process configures git through the environment (the `GIT_CONFIG_COUNT` protocol — a `safe.directory`, an `http.extraHeader` name, a mirror rewrite set without touching a config file): the two children the engine spawns from a scrubbed copy of its own environment — the default exec env's shell (`inheritEnv: "scrub"`) and an LSP server started without an explicit `env`.
|
|
17
|
+
- **Who is hurt / compensation.** A group whose key carries URL userinfo — a token-bearing `url.<base>.insteadOf`, and also a username-only one such as `url.ssh://git@github.com/.insteadOf` — is still dropped at that key, so git in the child still exits 128 there (fail-closed: the credential does not reach the child; KL-1129b); the compensation is to hand the group explicitly (`inheritEnv` allowlist / `shellEnv`) or move the rewrite into a git config file. `GIT_CONFIG_VALUE_<i>` can carry a credential (an `http.extraHeader: Authorization: …` value) and was already inherited: the shape rule never matched its name, and this change does not touch it either way (KL-1129a).
|
|
18
|
+
- Pins: `test/backlog1129-git-config-key-family.test.ts` (① `user.name` / `safe.directory` / a userinfo-free `insteadOf` through `scrubSecretEnv` and a real git child reading all three, and the default exec env's git child reading the entry while the withheld set names a planted credential and not the member; ② a token-bearing `insteadOf` key dropped with one finding, the token nowhere in the scrubbed env, git refusing with exit 128; the screen across schemes, a username-only userinfo, a userinfo with whitespace in it (git reads the credential out of it all the same) and an `@` in a path; ③ `isSecretEnvKey` without a value unchanged, with a clearing value `false`; an undefined entry drops; ④ the stem, decorated, padded, suffixed and credential names dropped by the shape rule even with a clearing value; the value member unmatched; the credential side winning a collision). Mutation-checked: a screen that always clears reds ②, a screen consulted without a value reds ③.
|
|
19
|
+
|
|
20
|
+
### The wiring manifest says whether a leg has hands — `WiringManifest.hands?: { mounted, reason? }` (#1039; additive; +1 optional member on `WiringManifest` / `WiringFacts`; +1 wire word `no_execution_env`; +0 exports; `configFingerprint` value moves once) (@server)
|
|
21
|
+
|
|
22
|
+
- **Before.** A leg whose deployment supplied no execution environment (no factory, no static `executionEnv`) mounts no hand band at all, and the wire said so only indirectly: an empty `mountedBy: "hands"` slice of `tools[]`, or `writeProtection.targetView: "spelling-only"` — which is absent altogether when the protection table is `[]`, where "no hands" and "protection off" read the same and the two assemblies hashed to the same `configFingerprint`.
|
|
23
|
+
- **Now.** Every engine-minted effective manifest carries `hands`, beside `lsp` and in the same shape: `{ mounted: true }` when the band mounted (a factory-minted env, or a static `executionEnv` seat that is not `undefined` — a supplied `StubExecutionEnv` included, the band mounting over it with file tools and no shell, and a JS caller's `null` likewise over the no-I/O placeholder), `{ mounted: false, reason: "no_execution_env" }` otherwise. The value is the one the hand band's mount dispatches on, relayed, not re-derived. `reason` is one literal, not a registered closed set, because the hands decision has one false branch (a second branch promotes it). Unlike `lsp`, the member is IN `configFingerprint` (an assembly fact, the class of `writeProtection`), so a with-hands and a hands-less assembly now hash apart. No `EngineNotice`, no `TaskResult` change; whether a full shell is reachable stays a `tools[]` reading.
|
|
24
|
+
- **Who is hurt.** A consumer that persisted `configFingerprint` values across engine versions sees every effective fingerprint change once (a new member in the hashed body); legs of one assembly still agree. An external `deriveWiringManifest` caller that passes an inconsistent `hands` fact is refused (the `autoMode` / `fileHistory` screen posture). **Compensation** n/a — the member is additive and its absence (older engines, the static half) is not a state.
|
|
25
|
+
- Pins: `test/backlog1039-wiring-manifest-hands.test.ts` (through the Runner: a static env, no env, a supplied `StubExecutionEnv`, a JS caller's `null`, the factory lane — each read against the same leg's `tools[]`; the fingerprint telling the two assemblies apart with no protection judge on either; the value screen's six refusals, `null` included; the static half; no aliasing). Docs: `docs/INTEGRATION-CORE.md` (`wiring_manifest` row). Wire sheet: `docs/wire-consumption/unreleased-1039.md`.
|
|
26
|
+
|
|
27
|
+
### Pins and wording — an explicit ask rule's ask and a hook's ask carry their own origin word on the card and on the park row (#1117; zero behavior face; +0 exports, +0 wire keys) (@server @cli)
|
|
28
|
+
|
|
29
|
+
- **What was unpinned.** The origin ladder already mints `ask_rule` from a decision's `matchedAskRule` and `hook` from the hook fold, and gate-level pins read the word the resolver receives — but no pin read the WIRE faces a surface renders from: `AskRequest.origin` on a live approver and `PendingAction.tool_approval.origin` / `CheckpointSummary.origin` on a park, through the real Runner. A host policy that raises an operator's must-ask as an `ask` WITH `matchedAskRule` (composed through `combinePolicies`, beside a base policy's plain ask) reaches both faces as `ask_rule` today; without the member it reads `policy`.
|
|
30
|
+
- **Now pinned**, and said where the words are listed: `docs/INTEGRATION-CORE.md` §4.6b (the enumerated set brought to its eleven words) states that `ask_rule` has one input — a minter raising an ask for a rule someone wrote must set `matchedAskRule`, or a plain policy ask reads `policy` on the card and the row — and what setting it means (the classifier does not answer it, a persisted allow rule does not clear it, an answer for this call does); the `PermissionResult.matchedAskRule` doc says the same. Two internal comments on the inherited-lane mint stations and the 7.26.0 `AskRequest.mandated` entry used "standing posture" for what a mandate refuses; the contract's word is a STANDING ANSWER (a release remembered from an earlier call) — an answer given for this call still clears a mandated ask. Wording only.
|
|
31
|
+
- Pins: `test/backlog1117-ask-origin-rule-and-hook.test.ts` (live card and park row for the operator ask folded beside a base ask and alone, with the no-`matchedAskRule` negative control reading `policy`; live card — over an allowing policy and folded over a policy ask — and park row for a hook ask). Mutation-checked: removing the ladder's `ask_rule` arm reds three, its `hook` arm two, the fold's `matchedAskRule` carry two.
|
|
32
|
+
|
|
33
|
+
### Forensics — the exit hard face says which of three causes a `no_pgid` receipt had (+2 trace lines; zero behavior face; +0 exports, +0 wire keys) (@server)
|
|
34
|
+
|
|
35
|
+
- `killAllBackgroundShellsNow` folds three facts into one `no_pgid` receipt: the env has no synchronous kill seat, the seat answered `no_pgid` itself, or the seat threw. Only the third left a trace line. Two more, same form, same opt-in (`SEMA_DEBUG_SHUTDOWN=1`, stderr): `hard kill: seat absent` and `hard kill: seat answered no_pgid`, each with `{ id, shellId }`. The seat is now read once, inside the guard, so a READ that throws (a revoked membrane) is traced as `seat threw` and costs only its row — before this, the presence check sat inside the guard too. Receipts, row states and the per-row `hard kill: row` line are unchanged; an answer outside the closed set is still folded to `no_pgid` with the per-row line only. Contract text: `docs/background-process-lifetime.md` (Receipts). Pins: `test/backlog1107-hard-kill-forensics.test.ts` (the trace sink mocked: three arms, one line each, receipts `no_pgid`, rows `running`; a throwing READ of the seat costs its row only and the next row is killed; the seat called with its env as `this`; a seat that killed or found the shell gone leaves no seat line).
|
|
36
|
+
|
|
37
|
+
### Test hygiene — `test/mcp-pagination.test.ts` no longer reads the host's `MCP_TOOL_TIMEOUT` (zero behavior face; +0 exports)
|
|
38
|
+
|
|
39
|
+
- The page-walk pins measure the DEFAULT budget; with the variable set in the host env two of them went red on any branch. The file now clears it for its own run and restores it after (red-before / green-after verified at the fix).
|
|
40
|
+
- `test/backlog994-s1-gaps-real-shells.test.ts`: the `echo x |& cat` pin compared the two pipeline stages' xtrace lines in a fixed order; the stages are concurrent processes, so the order is a race (one red in 23 546 on the stage run). The pin now compares membership; the other assertions of that pin are unchanged.
|
|
41
|
+
|
|
3
42
|
## 7.33.0 — 2026-09-26
|
|
4
43
|
|
|
5
44
|
### Model-call timeouts re-cut by segment: the header wait gets its own `firstByteTimeoutMs`, and every request carries the engine's dispatcher with the transport's header/body clocks off (#1121 #1122; behaviour: a pre-headers stall is a `stall` with its own one-retry lane, not a `transport` failure re-sent ten times; every watchdog defaults ON; +1 optional key `BrainTimeoutConfig.firstByteTimeoutMs`; **`BrainTimeoutConfig.connectTimeoutMs` REMOVED**; `StallTimeouts.connectMs` → `firstByteMs` (type-face breaking, see below); `WORKFLOW_AGENT_STALL_MS` 630 s → 1 500 s (derived); +1 dependency `undici@7.30.0`; `engines.node` `>=20.18.1`) (@server @cli @client-core)
|
|
@@ -1089,7 +1128,7 @@ with the next package release.
|
|
|
1089
1128
|
- **Who is hurt.** A caller that relied on the file backend folding an unnamed kind into a delete — none in-tree, and the behaviour was unreachable through a typed call. Exports +2 (`permissionRuleWriteContract`, `PermissionRuleWriteContractHooks`), removals 0.
|
|
1090
1129
|
- Reach: **@server** — the store twin's acceptance floor gains one suite to run (the sync kit is unchanged); **@cli** / **@client-core** — zero. Headless: a malformed delta from a foreign backend caller now throws where the file backend previously wrote a tombstone. `test/backlog996-write-delta-kinds.test.ts`.
|
|
1091
1130
|
### A mandate is a fact about the ask, not about a lane — `AskRequest.mandated` (#997; P2; approval face; additive, no export change)
|
|
1092
|
-
- **Before.** The only way an approval surface could learn that a card was MANDATED — that no persisted allow rule and no standing
|
|
1131
|
+
- **Before.** The only way an approval surface could learn that a card was MANDATED — that no persisted allow rule and no standing answer (a release remembered from an earlier call) could retire it, while an answer given for that call still could — was `AskRequest.ruleOffersAbsence === "mandated"`, a seat that reports what the RULE-OFFER LANE has to give. That lane speaks for one tool, only where a rule store is wired, and only on a task that can hold a rule; its own contract says absence is not a claim. Measured on the base of this entry, real Runner, `cat <path outside the session roots>`, both `shellGate` doctrines: `Bash` with a store and a principal carried `"mandated"`; the SAME command on `Monitor`, on `Bash` with no store, and on an anonymous task carried nothing. One approval card in every row; the mandate — the read station's own structural demotion — present in every row.
|
|
1093
1132
|
- **After.** `AskRequest.mandated?: true` (and the `PermissionResult` ask-arm member it is copied from) states it on the ask. Minted ONCE, at the gate's single origin stamp, from the one derivation the card's rule-offer word is also read from (`ruleClearanceOf`): the door SET decides the bit, the incumbent display ladder decides the word. Which door a card shows is a display choice; whether the ask is mandated is not — so a card worded `shadowed` over a probe mandate is mandated all the same, which the two-enumeration shape got wrong.
|
|
1094
1133
|
- **The doors,** in one place: the tool seat's `egress` / `irreversibility` marks, an operator `shellGate: "always"`, the shell read station's structural demotion or a probe that timed out or threw, a write the protection judge matched or could not resolve, `requiresRealApproval`, a hook-raised ask, and an ask decided at an ANCESTOR's frozen approver. **Not** a door: a person's own explicit `ask` permission rule — the lane words that `shadowed`, because a rule they wrote is already speaking.
|
|
1095
1134
|
- **What the bit says, and what it does not.** It says a persisted allow RULE may not clear the card. It does NOT say an approver may not answer it: a person, a hook and a deployment's blanket `onAsk: "allow"` seat all resolve a mandated ask exactly as before, and `requiresRealApproval` remains the separate, stronger "a person, not a configuration" demand. A shell whose POSTURE seat declines to auto-answer mandated cards reads this member to know which they are — @cli L-459, @client-core CC-110 (frame mirror), @server in transit. Absent on an older engine is not the opposite claim; tell the two apart by engine version, not by this seat.
|
|
@@ -6,7 +6,7 @@ import { stripUrlCredentials } from "./route-adjudicator.js";
|
|
|
6
6
|
import { classifyInputTooLong } from "./input-too-long.js";
|
|
7
7
|
import { FAST_MAX_BACKOFF_MS, providerWaitHint, retryBackoffMs } from "./retry.js";
|
|
8
8
|
import { bindBrainStatusEmitter, emitBrainStatus, emitBrainTelemetry } from "./status-sink.js";
|
|
9
|
-
import { createAttemptController,
|
|
9
|
+
import { createAttemptController, firstByteWindow, refuseRetiredTimeoutKnobs, resolveFirstByteRetries, resolveWatchdogMs, STALL_FIRST_BYTE_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, } from "./timeout.js";
|
|
10
10
|
import { readMember, thrownText } from "../core/thrown-value.js";
|
|
11
11
|
import { redactSecrets } from "../core/untrusted-egress.js";
|
|
12
12
|
const DEFAULT_MAX_RETRIES = 10;
|
|
@@ -334,6 +334,7 @@ export function runStreamingBrain(args) {
|
|
|
334
334
|
const firstByteTimeoutMs = resolveWatchdogMs(config.firstByteTimeoutMs, stallTimeouts?.firstByteMs, STALL_FIRST_BYTE_MS, "firstByteTimeoutMs");
|
|
335
335
|
const firstTokenTimeoutMs = resolveWatchdogMs(config.firstTokenTimeoutMs, stallTimeouts?.firstTokenMs, STALL_FIRST_TOKEN_MS, "firstTokenTimeoutMs");
|
|
336
336
|
const idleTimeoutMs = resolveWatchdogMs(config.idleTimeoutMs, stallTimeouts?.idleMs, STALL_IDLE_MS, "idleTimeoutMs");
|
|
337
|
+
const firstByteRetryBudget = resolveFirstByteRetries(config.firstByteRetries);
|
|
337
338
|
const armWaitDisclosure = (ceilingMs) => {
|
|
338
339
|
if (!(ceilingMs > WAITING_FIRST_TOKEN_AFTER_MS))
|
|
339
340
|
return { stop: () => undefined };
|
|
@@ -433,7 +434,7 @@ export function runStreamingBrain(args) {
|
|
|
433
434
|
cc.clearTimer();
|
|
434
435
|
try {
|
|
435
436
|
if (firstByteStall !== undefined) {
|
|
436
|
-
if (firstByteStalls >=
|
|
437
|
+
if (firstByteStalls >= firstByteRetryBudget)
|
|
437
438
|
throw firstByteStall.err;
|
|
438
439
|
firstByteStalls++;
|
|
439
440
|
const announcement = retryAnnouncement(firstByteStall);
|
|
@@ -448,7 +449,7 @@ export function runStreamingBrain(args) {
|
|
|
448
449
|
retryInSec: Math.ceil(remainingMs / 1000),
|
|
449
450
|
retryInMs: remainingMs,
|
|
450
451
|
attempt: firstByteStalls,
|
|
451
|
-
maxRetries:
|
|
452
|
+
maxRetries: firstByteRetryBudget,
|
|
452
453
|
errClass: announcement.errClass,
|
|
453
454
|
}));
|
|
454
455
|
continue;
|
package/dist/brain/timeout.d.ts
CHANGED
|
@@ -88,10 +88,13 @@ export declare function refuseRetiredTimeoutKnobs(holder: object | undefined, le
|
|
|
88
88
|
* reference implementation's allowance) — a large context on a slow uplink is still UPLOADING when a
|
|
89
89
|
* fixed window would call the gateway silent. */
|
|
90
90
|
export declare const FIRST_BYTE_UPLOAD_BYTES_PER_SECOND = 32768;
|
|
91
|
-
/** A first-byte stall is re-sent at most this many times
|
|
92
|
-
* `maxRetries` (the reference implementation: "normally one retry, not the
|
|
93
|
-
* ladder counts transport failures, and this is not one. */
|
|
91
|
+
/** A first-byte stall is re-sent at most this many times by default ({@link BrainTimeoutConfig.firstByteRetries}),
|
|
92
|
+
* out of its OWN budget rather than `maxRetries` (the reference implementation: "normally one retry, not the
|
|
93
|
+
* session budget"); the shared ladder counts transport failures, and this is not one. */
|
|
94
94
|
export declare const FIRST_BYTE_RETRY_BUDGET = 1;
|
|
95
|
+
/** The first-byte lane's retry budget: absent ⇒ {@link FIRST_BYTE_RETRY_BUDGET}; `0` / `1` are themselves; anything
|
|
96
|
+
* else is refused (`config.stall_timeout_invalid`), never folded to the default or to `0`. */
|
|
97
|
+
export declare function resolveFirstByteRetries(value: unknown): 0 | 1;
|
|
95
98
|
/** The retry's knob is the first times this — the reference implementation widens its retry to a second
|
|
96
99
|
* knob's default (599s against a 300s first window); one knob here keeps that ratio instead. */
|
|
97
100
|
export declare const FIRST_BYTE_RETRY_WINDOW_FACTOR = 2;
|
|
@@ -125,10 +128,17 @@ export interface BrainTimeoutConfig {
|
|
|
125
128
|
* `fetch()` call → response headers, ms: the window a gateway has to START answering (until 7.33.0 a
|
|
126
129
|
* knob named `connectTimeoutMs` bounded this wait; it is gone — the connect clock is the transport's). Absent ⇒
|
|
127
130
|
* {@link STALL_FIRST_BYTE_MS}; the armed window grows with the request body ({@link firstByteWindow});
|
|
128
|
-
* a stall earns ONE retry at a widened window
|
|
131
|
+
* a stall earns ONE retry at a widened window ({@link firstByteRetries}), then fails naming this knob.
|
|
129
132
|
* Fires as a `stall` (this process cancelled its own request), never as a transport fault. `0` ⇒ off.
|
|
130
133
|
*/
|
|
131
134
|
firstByteTimeoutMs?: number;
|
|
135
|
+
/**
|
|
136
|
+
* The first-byte lane's own retry budget: `1` (absent ⇒ {@link FIRST_BYTE_RETRY_BUDGET}) or `0` — the first
|
|
137
|
+
* window's expiry is the call's terminal, for a host whose own failover ladder should take a silent gateway after
|
|
138
|
+
* one window. Independent of `maxRetries` both ways; construction only; any other value fails the call naming this
|
|
139
|
+
* knob. The derived floors ({@link firstByteLaneKnobMs}) read the default.
|
|
140
|
+
*/
|
|
141
|
+
firstByteRetries?: 0 | 1;
|
|
132
142
|
/**
|
|
133
143
|
* After the stream opens, cancel it if no content delta (text / thinking / tool input) arrives
|
|
134
144
|
* within this many ms → `BrainError("network", "first-token timeout")`. For reasoning models the
|
package/dist/brain/timeout.js
CHANGED
|
@@ -34,6 +34,16 @@ export function refuseRetiredTimeoutKnobs(holder, level, where) {
|
|
|
34
34
|
}
|
|
35
35
|
export const FIRST_BYTE_UPLOAD_BYTES_PER_SECOND = 32_768;
|
|
36
36
|
export const FIRST_BYTE_RETRY_BUDGET = 1;
|
|
37
|
+
export function resolveFirstByteRetries(value) {
|
|
38
|
+
if (value === undefined)
|
|
39
|
+
return FIRST_BYTE_RETRY_BUDGET;
|
|
40
|
+
if (value === 0 || value === 1)
|
|
41
|
+
return value;
|
|
42
|
+
const shown = typeof value === "string" ? JSON.stringify(value) : typeof value === "bigint" ? `${value}n` : typeof value === "object" && value !== null ? "an object" : String(value);
|
|
43
|
+
const e = new Error(`firstByteRetries must be 0 or 1 (got ${shown}) — 1 re-sends a first-byte stall once at a widened window (the default), 0 ends the call at the first window so the caller's own failover takes it`);
|
|
44
|
+
e.code = "config.stall_timeout_invalid";
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
37
47
|
export const FIRST_BYTE_RETRY_WINDOW_FACTOR = 2;
|
|
38
48
|
export function resolveWatchdogMs(configured, advisory, engineDefault, knob) {
|
|
39
49
|
return resolveStallTimeoutMs(configured ?? advisory, knob) ?? engineDefault;
|
|
@@ -11,7 +11,7 @@ import { inlineUntrusted } from "../untrusted-text.js";
|
|
|
11
11
|
import { mountWriteProtection } from "./write-protection-station.js";
|
|
12
12
|
import { autoRunOnRecommendationEffective } from "./memory-consolidation-driver.js";
|
|
13
13
|
import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
|
|
14
|
-
import { LSP_CONTRACT_ID, countElicitOptIns, deriveLspSeam, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
|
|
14
|
+
import { LSP_CONTRACT_ID, countElicitOptIns, deriveHandsSeam, deriveLspSeam, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
|
|
15
15
|
import { DEFAULT_IRREVERSIBLE_SCOPE } from "./checkpoint-scope.js";
|
|
16
16
|
export function mcpManifestEntries(entries, statuses) {
|
|
17
17
|
return (entries ?? []).map((entry) => {
|
|
@@ -218,6 +218,7 @@ export function prepareWiringManifest(input) {
|
|
|
218
218
|
tools: toolRoster,
|
|
219
219
|
hooks: wiredHookEntries(hooks, spec.hooks !== undefined ? "spec" : "deps", preToolUseObservational),
|
|
220
220
|
...(lspManagerWired ? { lsp: deriveLspSeam(toolRoster.entries.some((e) => e.contract.contractId === LSP_CONTRACT_ID), lspDiagnosticsLive) } : {}),
|
|
221
|
+
hands: deriveHandsSeam(handsEnabled),
|
|
221
222
|
fileHistory: fileHistoryFact,
|
|
222
223
|
});
|
|
223
224
|
peerLaneRefs.askEffective = wiringManifest.ask.effective;
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { RedactionConfidence, RedactionSource } from "./arg-summary.js";
|
|
20
20
|
import { type EngineNotice } from "./engine-notice.js";
|
|
21
|
+
/** A name table as the exact-name arm reads it: membership of an UPPER-CASED name, given the entry's value when
|
|
22
|
+
* the caller has one. A `Set` of exact names is one (it ignores the value); the exemption side is the union of
|
|
23
|
+
* its two tables. */
|
|
24
|
+
export interface EnvNameTable {
|
|
25
|
+
has(upperName: string, value?: string): boolean;
|
|
26
|
+
}
|
|
21
27
|
/** Which rule classified a key as secret-shaped — the env arm's `kind` vocabulary. */
|
|
22
28
|
export type SecretEnvFindingKind = "suffix-rule" | "namespace-rule" | "exact-name";
|
|
23
29
|
/** One env entry the `scrub` policy dropped: the key (the VALUE is never carried — this record is meant
|
|
@@ -30,14 +36,17 @@ export interface SecretEnvFinding {
|
|
|
30
36
|
}
|
|
31
37
|
/** What the two exact-name lists say about a name, before any shape rule is consulted. */
|
|
32
38
|
export type ExactEnvNameVerdict = "credential" | "exempt" | "unknown";
|
|
33
|
-
/** The exact-name arm of the classification, taking both lists as arguments
|
|
39
|
+
/** The exact-name arm of the classification, taking both lists as arguments (the exemption side is the exact
|
|
40
|
+
* names plus the named families). The credential list is read
|
|
34
41
|
* FIRST: an exemption can never take a name away from it, so a name landing on both lists resolves to the
|
|
35
42
|
* credential reading — the fail-closed direction (a wrongly-exempted name leaks; a wrongly-dropped one
|
|
36
43
|
* does not). Module-level export (not part of the package surface): the two real lists are disjoint, so
|
|
37
44
|
* only a controlled collision can tell this precedence from an inverted one. */
|
|
38
|
-
export declare function classifyExactEnvName(key: string, credentialNames:
|
|
39
|
-
/** Is `key` a secret-pattern env var (one the fail-closed `scrub` policy drops before spawning a shell)?
|
|
40
|
-
|
|
45
|
+
export declare function classifyExactEnvName(key: string, credentialNames: EnvNameTable, exemptNames: EnvNameTable, value?: string): ExactEnvNameVerdict;
|
|
46
|
+
/** Is `key` a secret-pattern env var (one the fail-closed `scrub` policy drops before spawning a shell)? Pass the
|
|
47
|
+
* entry's `value` to get exactly the scrub's answer for that entry: a named-family exemption holds only for a value
|
|
48
|
+
* that clears the family's screen, so without a value such a member reads as secret (the name alone cannot clear). */
|
|
49
|
+
export declare function isSecretEnvKey(key: string, value?: string): boolean;
|
|
41
50
|
/** Drop every secret-pattern key (see {@link isSecretEnvKey}) from an env object — the fail-closed `scrub`
|
|
42
51
|
* policy. `findings` is an OPTIONAL caller-owned collector: pass an array to learn which entries were
|
|
43
52
|
* dropped and why, read it back after the call. Omitting it leaves this function byte-for-byte what it
|
package/dist/core/secret-env.js
CHANGED
|
@@ -15,21 +15,30 @@ const NON_SECRET_ENV_EXACT_NAMES = new Set([
|
|
|
15
15
|
"MAX_BATCH_PREFILL_TOKENS", "MAX_WAITING_TOKENS", "MAX_TOP_N_TOKENS", "RATE_LIMIT_TOKENS",
|
|
16
16
|
"PROMPT_TOKENS", "CONTEXT_TOKENS",
|
|
17
17
|
]);
|
|
18
|
+
const NON_SECRET_ENV_INDEXED_FAMILIES = [
|
|
19
|
+
{ stem: "GIT_CONFIG_KEY", valueClears: (value) => !/[a-z][a-z0-9+.-]*:\/\/[^/@]+@/i.test(value) },
|
|
20
|
+
];
|
|
21
|
+
const indexedFamilyMember = (stem) => new RegExp(`^${stem}_(?:0|[1-9][0-9]*)$`);
|
|
22
|
+
const NON_SECRET_ENV_INDEXED_FAMILY_ROWS = NON_SECRET_ENV_INDEXED_FAMILIES.map((f) => ({ member: indexedFamilyMember(f.stem), valueClears: f.valueClears }));
|
|
23
|
+
const NON_SECRET_ENV_NAMES = {
|
|
24
|
+
has: (upperName, value) => NON_SECRET_ENV_EXACT_NAMES.has(upperName) ||
|
|
25
|
+
(value !== undefined && NON_SECRET_ENV_INDEXED_FAMILY_ROWS.some((row) => row.member.test(upperName) && row.valueClears(value))),
|
|
26
|
+
};
|
|
18
27
|
const SECRET_ENV_KIND_CONFIDENCE = {
|
|
19
28
|
"exact-name": "high",
|
|
20
29
|
"suffix-rule": "medium",
|
|
21
30
|
"namespace-rule": "medium",
|
|
22
31
|
};
|
|
23
|
-
export function classifyExactEnvName(key, credentialNames, exemptNames) {
|
|
32
|
+
export function classifyExactEnvName(key, credentialNames, exemptNames, value) {
|
|
24
33
|
const upper = key.toUpperCase();
|
|
25
|
-
if (credentialNames.has(upper))
|
|
34
|
+
if (credentialNames.has(upper, value))
|
|
26
35
|
return "credential";
|
|
27
|
-
if (exemptNames.has(upper))
|
|
36
|
+
if (exemptNames.has(upper, value))
|
|
28
37
|
return "exempt";
|
|
29
38
|
return "unknown";
|
|
30
39
|
}
|
|
31
|
-
function classifySecretEnvKey(key) {
|
|
32
|
-
const byExactName = classifyExactEnvName(key, SECRET_ENV_EXACT_NAMES,
|
|
40
|
+
function classifySecretEnvKey(key, value) {
|
|
41
|
+
const byExactName = classifyExactEnvName(key, SECRET_ENV_EXACT_NAMES, NON_SECRET_ENV_NAMES, value);
|
|
33
42
|
if (byExactName === "credential")
|
|
34
43
|
return "exact-name";
|
|
35
44
|
if (byExactName === "exempt")
|
|
@@ -38,13 +47,13 @@ function classifySecretEnvKey(key) {
|
|
|
38
47
|
return "suffix-rule";
|
|
39
48
|
return SEMA_NAMESPACE_SECRET_RE.test(key) ? "namespace-rule" : undefined;
|
|
40
49
|
}
|
|
41
|
-
export function isSecretEnvKey(key) {
|
|
42
|
-
return classifySecretEnvKey(key) !== undefined;
|
|
50
|
+
export function isSecretEnvKey(key, value) {
|
|
51
|
+
return classifySecretEnvKey(key, value) !== undefined;
|
|
43
52
|
}
|
|
44
53
|
export function scrubSecretEnv(env, findings) {
|
|
45
54
|
const out = {};
|
|
46
55
|
for (const k of Object.keys(env)) {
|
|
47
|
-
const kind = classifySecretEnvKey(k);
|
|
56
|
+
const kind = classifySecretEnvKey(k, env[k]);
|
|
48
57
|
if (kind === undefined) {
|
|
49
58
|
out[k] = env[k];
|
|
50
59
|
continue;
|
|
@@ -606,7 +606,12 @@ export class TaskRegistry {
|
|
|
606
606
|
}
|
|
607
607
|
let outcome;
|
|
608
608
|
try {
|
|
609
|
-
|
|
609
|
+
const seat = handle.env.killBackgroundNow;
|
|
610
|
+
if (seat === undefined)
|
|
611
|
+
shutdownDebug("hard kill: seat absent", { id: handle.id, shellId: handle.shellId });
|
|
612
|
+
outcome = seat === undefined ? "no_pgid" : seat.call(handle.env, handle.shellId);
|
|
613
|
+
if (seat !== undefined && outcome === "no_pgid")
|
|
614
|
+
shutdownDebug("hard kill: seat answered no_pgid", { id: handle.id, shellId: handle.shellId });
|
|
610
615
|
}
|
|
611
616
|
catch (err) {
|
|
612
617
|
shutdownDebug("hard kill: seat threw", { id: handle.id, shellId: handle.shellId, error: thrownText(err) });
|
|
@@ -341,18 +341,18 @@ export type PermissionResult = {
|
|
|
341
341
|
* `probe*` above; ENGINE-STAMPED and VALIDATED at the tighten, and deliberately NOT merged onto an ask
|
|
342
342
|
* another layer raised, which a grant leaves standing. */
|
|
343
343
|
probeReadRootCandidate?: import("./types.js").ReadRootGrantCandidate;
|
|
344
|
-
/** An EXPLICIT `ask` permission rule matched this call (design/127 DSL — the CC `alwaysAskRules`
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
344
|
+
/** An EXPLICIT `ask` permission rule matched this call (design/127 DSL — the CC `alwaysAskRules` shape); carries the
|
|
345
|
+
* matched rule's text. Present ⇔ a rule someone WROTE says "ask about this", never for a `defaultAction:"ask"` fallback
|
|
346
|
+
* (an unmatched call is default-closed posture, not a per-call instruction — CC draws the same line: only a matched ask
|
|
347
|
+
* rule diverts the auto-mode lane, a mode-default ask stays classifier-eligible). Consumer: the gate's auto-mode
|
|
348
|
+
* classifier step refuses to resolve an ask carrying it — a person's standing "ask me each time" is not classifier
|
|
349
|
+
* hesitation, so a classifier verdict must not be the thing that clears it. A policy self-declaring the member only
|
|
350
|
+
* ever OPTS ITS OWN ask OUT of classifier auto-resolution (tightening — the false-claim direction is safe, unlike the
|
|
351
|
+
* engine-stamped `"hook"` word whose risk was laundering provenance AWAY). {@link combinePolicies} carries it onto the
|
|
352
|
+
* surviving ask when ANY folded-away concurrent ask bore it (monotone, tighten-only); it is consumed inside the gate
|
|
353
|
+
* and NOT copied onto the park/approval-card request — but it is the ONE input of the `ask_rule` origin word they DO
|
|
354
|
+
* carry ({@link origin}): a minter raising an ask for a written rule (a person's settings rule, an operator's must-ask
|
|
355
|
+
* list) that leaves it unset gets `policy` there, indistinguishable from hesitation. */
|
|
356
356
|
matchedAskRule?: string;
|
|
357
357
|
/** design/252 G-2 (additive): the RULE-PROVENANCE evidence behind this ask — see
|
|
358
358
|
* {@link AskRuleEvidence}. ENGINE-STAMPED inside the gate, once, after the org layer and the
|
|
@@ -137,6 +137,15 @@ export interface WiringManifestMcpEntry {
|
|
|
137
137
|
* both. A leg whose `LSP` tool was excluded can still be pushing diagnostics. */
|
|
138
138
|
export declare const LSP_LANES: readonly ["tool", "diagnostics", "tool_and_diagnostics"];
|
|
139
139
|
export type LspLane = (typeof LSP_LANES)[number];
|
|
140
|
+
/** Why a leg's hand band is NOT mounted ({@link WiringManifest.hands}`.reason`), one word per false branch of the
|
|
141
|
+
* hands decision (`prepare-execution-env.ts`); it has one: `no_execution_env` = neither env seat supplied one. A
|
|
142
|
+
* literal, not a registered closed set (the gate admits sets of two or more) until a second branch exists. */
|
|
143
|
+
export type HandsUnmountedReason = "no_execution_env";
|
|
144
|
+
/** The manifest's hands seam from the leg's one hands fact (see {@link WiringManifest.hands}). */
|
|
145
|
+
export declare function deriveHandsSeam(handsMounted: boolean): {
|
|
146
|
+
mounted: boolean;
|
|
147
|
+
reason?: HandsUnmountedReason;
|
|
148
|
+
};
|
|
140
149
|
/** The contract id the LSP tool mounts under (`createLspTool`) — the roster's structural discriminator, which
|
|
141
150
|
* a disclosure keys on instead of re-spelling the wire name. */
|
|
142
151
|
export declare const LSP_CONTRACT_ID = "core.lsp@1";
|
|
@@ -347,6 +356,19 @@ export interface WiringManifest {
|
|
|
347
356
|
mounted: boolean;
|
|
348
357
|
lane?: LspLane;
|
|
349
358
|
};
|
|
359
|
+
/**
|
|
360
|
+
* EFFECTIVE half only, ALWAYS present on an engine-minted effective manifest: is this leg's HAND band (the file
|
|
361
|
+
* tools, and the shell tools where the env has a shell) mounted — the value the band's mount reads, stated
|
|
362
|
+
* positively instead of inferred from `tools[]` or the write-protection word. `true` ⇔ the factory minted an env
|
|
363
|
+
* or the static `executionEnv` seat is not `undefined` (a JS caller's `null` and a `StubExecutionEnv` count: the
|
|
364
|
+
* band mounts over them, without a shell); `false` carries `reason` ({@link HandsUnmountedReason}), which is absent
|
|
365
|
+
* beside `true`. Whether a full shell is reachable is `tools[]`'s fact. ABSENT = an older mint or an external
|
|
366
|
+
* derivation, never a state. IN {@link configFingerprint}: an ASSEMBLY fact (the {@link writeProtection} class).
|
|
367
|
+
*/
|
|
368
|
+
hands?: {
|
|
369
|
+
mounted: boolean;
|
|
370
|
+
reason?: HandsUnmountedReason;
|
|
371
|
+
};
|
|
350
372
|
/**
|
|
351
373
|
* EFFECTIVE half only, PRESENT IFF this leg compiled a write-protection judge — a deployment that
|
|
352
374
|
* replaced the table with `[]` opted the layer out entirely, and reports nothing here rather than a
|
|
@@ -497,6 +519,11 @@ export interface WiringFacts {
|
|
|
497
519
|
mounted: boolean;
|
|
498
520
|
lane?: LspLane;
|
|
499
521
|
};
|
|
522
|
+
/** Effective half only — the hands seam ({@link WiringManifest.hands}, {@link deriveHandsSeam}); the engine always passes it. */
|
|
523
|
+
hands?: {
|
|
524
|
+
mounted: boolean;
|
|
525
|
+
reason?: HandsUnmountedReason;
|
|
526
|
+
};
|
|
500
527
|
/** Effective half only — the compiled write-protection judge's OWN target-view word
|
|
501
528
|
* ({@link WiringManifest.writeProtection}); absent ⇒ this leg compiled no judge (an emptied table). */
|
|
502
529
|
writeProtectionTargetView?: WriteProtectionTargetView;
|
|
@@ -5,6 +5,9 @@ import { isLiveQuestionFace } from "./ask-question.js";
|
|
|
5
5
|
import { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
|
|
6
6
|
export const AUTO_MODE_ARM_REASONS = ["armed", "no_intent", "no_face", "denied", "resolver_fault"];
|
|
7
7
|
export const LSP_LANES = ["tool", "diagnostics", "tool_and_diagnostics"];
|
|
8
|
+
export function deriveHandsSeam(handsMounted) {
|
|
9
|
+
return handsMounted ? { mounted: true } : { mounted: false, reason: "no_execution_env" };
|
|
10
|
+
}
|
|
8
11
|
export const LSP_CONTRACT_ID = "core.lsp@1";
|
|
9
12
|
export function deriveLspSeam(mounted, diagnosticsLive) {
|
|
10
13
|
const lane = mounted ? (diagnosticsLive ? "tool_and_diagnostics" : "tool") : diagnosticsLive ? "diagnostics" : undefined;
|
|
@@ -84,6 +87,13 @@ function readAutoModeFact(fact) {
|
|
|
84
87
|
}
|
|
85
88
|
return { armed, reason, ...(deniedSource !== undefined ? { deniedSource } : {}) };
|
|
86
89
|
}
|
|
90
|
+
function readHandsFact(fact) {
|
|
91
|
+
const { mounted, reason } = typeof fact === "object" && fact !== null ? fact : { mounted: undefined, reason: undefined };
|
|
92
|
+
if (typeof mounted !== "boolean" || (mounted ? reason !== undefined : reason !== "no_execution_env")) {
|
|
93
|
+
throw new Error(`the hands wiring fact is inconsistent (${JSON.stringify(fact)}) — mounted is a boolean, and reason stands exactly when it is false, as "no_execution_env"`);
|
|
94
|
+
}
|
|
95
|
+
return mounted ? { mounted } : { mounted, reason };
|
|
96
|
+
}
|
|
87
97
|
function readFileHistoryFact(word) {
|
|
88
98
|
if (word !== "on" && word !== "disabled")
|
|
89
99
|
throw new Error(`the file-history wiring fact ${JSON.stringify(word)} is not one of on|disabled`);
|
|
@@ -146,6 +156,7 @@ export function deriveWiringManifest(facts) {
|
|
|
146
156
|
...(facts.half === "effective" && facts.tools !== undefined ? { tools: facts.tools } : {}),
|
|
147
157
|
...(facts.half === "effective" && facts.hooks !== undefined && facts.hooks.length > 0 ? { hooks: facts.hooks.map((h) => ({ ...h })) } : {}),
|
|
148
158
|
...(facts.half === "effective" && facts.lsp !== undefined ? { lsp: { ...facts.lsp } } : {}),
|
|
159
|
+
...(facts.half === "effective" && facts.hands !== undefined ? { hands: readHandsFact(facts.hands) } : {}),
|
|
149
160
|
...(facts.half === "effective" && facts.writeProtectionTargetView !== undefined ? { writeProtection: { targetView: facts.writeProtectionTargetView } } : {}),
|
|
150
161
|
...(facts.half === "effective" && facts.readDenyBuiltinTiersActive !== undefined && facts.readDenyBuiltinTiersActive.length > 0
|
|
151
162
|
? { readDeny: { builtinTiers: [...facts.readDenyBuiltinTiersActive] } }
|
package/package.json
CHANGED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
{
|
|
2
|
+
"repo": "core",
|
|
3
|
+
"version": "7.33.1",
|
|
4
|
+
"from": "88d7026bbe5019e8a8b1158551e23b1bf5adb53e",
|
|
5
|
+
"to": "v7.33.1",
|
|
6
|
+
"added": [],
|
|
7
|
+
"removed": [],
|
|
8
|
+
"changed": [],
|
|
9
|
+
"narrowed": [],
|
|
10
|
+
"closedSetMembers": [
|
|
11
|
+
{
|
|
12
|
+
"set": "HandsUnmountedReason",
|
|
13
|
+
"at": [
|
|
14
|
+
"src/core/wiring-manifest.ts:168"
|
|
15
|
+
],
|
|
16
|
+
"newSet": true,
|
|
17
|
+
"added": [
|
|
18
|
+
"no_execution_env"
|
|
19
|
+
],
|
|
20
|
+
"removed": [],
|
|
21
|
+
"breaking": false,
|
|
22
|
+
"criteriaCovered": false
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"closedSetsUnreadable": [],
|
|
26
|
+
"typeKeys": [
|
|
27
|
+
{
|
|
28
|
+
"type": "AnthropicBrainConfig",
|
|
29
|
+
"at": [
|
|
30
|
+
"src/brain/anthropic.ts:25"
|
|
31
|
+
],
|
|
32
|
+
"added": [
|
|
33
|
+
"firstByteRetries"
|
|
34
|
+
],
|
|
35
|
+
"addedDetail": [
|
|
36
|
+
{
|
|
37
|
+
"key": "firstByteRetries",
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"addedRequired": [],
|
|
42
|
+
"removed": [],
|
|
43
|
+
"breaking": false,
|
|
44
|
+
"criteriaCovered": false
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "BrainTimeoutConfig",
|
|
48
|
+
"at": [
|
|
49
|
+
"src/brain/timeout.ts:179"
|
|
50
|
+
],
|
|
51
|
+
"added": [
|
|
52
|
+
"firstByteRetries"
|
|
53
|
+
],
|
|
54
|
+
"addedDetail": [
|
|
55
|
+
{
|
|
56
|
+
"key": "firstByteRetries",
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"addedRequired": [],
|
|
61
|
+
"removed": [],
|
|
62
|
+
"breaking": false,
|
|
63
|
+
"criteriaCovered": true
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "OpenAIBrainConfig",
|
|
67
|
+
"at": [
|
|
68
|
+
"src/brain/openai.ts:27"
|
|
69
|
+
],
|
|
70
|
+
"added": [
|
|
71
|
+
"firstByteRetries"
|
|
72
|
+
],
|
|
73
|
+
"addedDetail": [
|
|
74
|
+
{
|
|
75
|
+
"key": "firstByteRetries",
|
|
76
|
+
"optional": true
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"addedRequired": [],
|
|
80
|
+
"removed": [],
|
|
81
|
+
"breaking": false,
|
|
82
|
+
"criteriaCovered": false
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"type": "OpenResponsesBrainConfig",
|
|
86
|
+
"at": [
|
|
87
|
+
"src/brain/open-responses.ts:101"
|
|
88
|
+
],
|
|
89
|
+
"added": [
|
|
90
|
+
"firstByteRetries"
|
|
91
|
+
],
|
|
92
|
+
"addedDetail": [
|
|
93
|
+
{
|
|
94
|
+
"key": "firstByteRetries",
|
|
95
|
+
"optional": true
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"addedRequired": [],
|
|
99
|
+
"removed": [],
|
|
100
|
+
"breaking": false,
|
|
101
|
+
"criteriaCovered": false
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"type": "StreamEngineConfig",
|
|
105
|
+
"at": [
|
|
106
|
+
"src/brain/stream-engine.ts:45"
|
|
107
|
+
],
|
|
108
|
+
"via": "AnthropicBrainConfig.StreamEngineConfig",
|
|
109
|
+
"added": [
|
|
110
|
+
"firstByteRetries"
|
|
111
|
+
],
|
|
112
|
+
"addedDetail": [
|
|
113
|
+
{
|
|
114
|
+
"key": "firstByteRetries",
|
|
115
|
+
"optional": true
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"addedRequired": [],
|
|
119
|
+
"removed": [],
|
|
120
|
+
"breaking": false,
|
|
121
|
+
"criteriaCovered": false
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"type": "WiringFacts",
|
|
125
|
+
"at": [
|
|
126
|
+
"src/core/wiring-manifest.ts:451"
|
|
127
|
+
],
|
|
128
|
+
"added": [
|
|
129
|
+
"hands"
|
|
130
|
+
],
|
|
131
|
+
"addedDetail": [
|
|
132
|
+
{
|
|
133
|
+
"key": "hands",
|
|
134
|
+
"optional": true
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
"addedRequired": [],
|
|
138
|
+
"removed": [],
|
|
139
|
+
"breaking": false,
|
|
140
|
+
"criteriaCovered": false
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"type": "WiringManifest",
|
|
144
|
+
"at": [
|
|
145
|
+
"src/core/wiring-manifest.ts:193"
|
|
146
|
+
],
|
|
147
|
+
"added": [
|
|
148
|
+
"hands"
|
|
149
|
+
],
|
|
150
|
+
"addedDetail": [
|
|
151
|
+
{
|
|
152
|
+
"key": "hands",
|
|
153
|
+
"optional": true
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"addedRequired": [],
|
|
157
|
+
"removed": [],
|
|
158
|
+
"breaking": false,
|
|
159
|
+
"criteriaCovered": true
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
"typeKeysUnreadableArms": [],
|
|
163
|
+
"typeKeysFlattenedCount": 217,
|
|
164
|
+
"declarationMoves": [],
|
|
165
|
+
"mintMoves": [],
|
|
166
|
+
"criteriaObserves": [
|
|
167
|
+
{
|
|
168
|
+
"kind": "taskEventField",
|
|
169
|
+
"name": "wiring_manifest.manifest",
|
|
170
|
+
"assertions": [
|
|
171
|
+
"G3.a"
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"kind": "typeMember",
|
|
176
|
+
"name": "BrainTimeoutConfig.firstByteRetries",
|
|
177
|
+
"assertions": [
|
|
178
|
+
"G1.a",
|
|
179
|
+
"G1.b"
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"kind": "typeMember",
|
|
184
|
+
"name": "BrainTimeoutConfig.firstByteTimeoutMs",
|
|
185
|
+
"assertions": [
|
|
186
|
+
"G1.a"
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"kind": "typeMember",
|
|
191
|
+
"name": "SecretEnvFinding.key",
|
|
192
|
+
"assertions": [
|
|
193
|
+
"G2.a"
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"kind": "typeMember",
|
|
198
|
+
"name": "SecretEnvFinding.kind",
|
|
199
|
+
"assertions": [
|
|
200
|
+
"G2.a",
|
|
201
|
+
"G2.b"
|
|
202
|
+
]
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"kind": "typeMember",
|
|
206
|
+
"name": "WiringManifest.configFingerprint",
|
|
207
|
+
"assertions": [
|
|
208
|
+
"G3.b"
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"kind": "typeMember",
|
|
213
|
+
"name": "WiringManifest.hands",
|
|
214
|
+
"assertions": [
|
|
215
|
+
"G3.a",
|
|
216
|
+
"G3.b"
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"consumersHint": {
|
|
221
|
+
"cli": [
|
|
222
|
+
"WiringFacts",
|
|
223
|
+
"WiringManifest"
|
|
224
|
+
],
|
|
225
|
+
"server": [
|
|
226
|
+
"WiringFacts",
|
|
227
|
+
"WiringManifest"
|
|
228
|
+
],
|
|
229
|
+
"sdk": [
|
|
230
|
+
"WiringManifest"
|
|
231
|
+
],
|
|
232
|
+
"note": "scanned cli@4e593722 server@7a9240f9 sdk@4031f42 — a name listed is MENTIONED there today, which is evidence of consumption, not proof of it. Ledger files (**/docs/core-consumption/**, **/docs/wire-consumption/**, **/*LEDGER*.json) are not evidence of use — server: 4 name(s) matched ONLY in bookkeeping files and were set aside (AnthropicBrainConfig, BrainTimeoutConfig, OpenAIBrainConfig, OpenResponsesBrainConfig)"
|
|
233
|
+
},
|
|
234
|
+
"breakingCount": 0,
|
|
235
|
+
"breakingItems": [],
|
|
236
|
+
"toResolvedAt": "093aeef4709628f9cabb0d4b1c88621c98d57bda"
|
|
237
|
+
}
|