@sema-agent/core 7.35.0 → 7.35.2

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 CHANGED
@@ -1,3 +1,60 @@
1
+ ## 7.35.2 — 2026-09-29
2
+
3
+ ### A wait the watchdog cuts at the 30 s disclosure threshold is disclosed before the cut (#746; behavior (status frames: +1 `waiting_first_token` frame per attempt when a first-token or first-byte window is exactly the 30 s threshold); +0 exports, +0 wire keys, +0 codes, closed sets unchanged) (@cli @client-core @server @test)
4
+
5
+ - **Before (v7.35.1).** Measured by cli against a running engine (L-778, five scenarios: a gateway that sends headers at once and its first token 45 s later, or holds its headers 45 s). With the first-token watchdog at 30 s (`firstTokenTimeoutMs`; server env `MODEL_FIRST_TOKEN_TIMEOUT_MS`), a request waited 30 s with no `status` frame at all and was then cut. The wait disclosure was armed only for a ceiling strictly above its 30 s threshold, so the one tick that fell on the deadline was never sent. At 40 s, or at the 600 s default, the 30 s frame was there. A first-byte window of exactly 30 s (knob plus upload allowance) had the same gap.
6
+ - **Now.** The wait disclosure is armed whenever its ceiling reaches the threshold (`>=`, the watchdog's own comparison). When the watchdog fires, a wait not yet disclosed is disclosed at that instant — `waiting_first_token` with `elapsedMs` equal to `timeoutMs` — and the retry (or terminal) frame follows. The deadline owns that frame, so which timer runs first no longer decides whether it is sent. This holds for the first-token wait and the header wait alike. Re-announcements are unchanged: they stay strictly before the ceiling, so a 120 s watchdog still shows 30 / 60 / 90 s and no frame at 120 s.
7
+ - **Not changed.** The retry after a watchdog cut keeps `phase: "reconnecting"` with `errClass: "stall"`. The shells already tell the two apart by `errClass`: cli L-264 renders it as "No output before the stream timeout · slow model or gateway", through client-core's `stalled` arm. The engine does not rename the phase. The header wait and the first-token wait still share `waiting_first_token`; a separate phase word would be a wire change.
8
+ - **Readings (engine fixtures of cli's scenarios, fake clock).**
9
+ - A (token 45 s, watchdog 30 s, every request slow): 0 → 11 wait frames, one per attempt, each 30000 of 30000, each before its attempt's `reconnecting` / `stall` frame. The call still ends `gave_up` / `first-token timeout after 30000ms`.
10
+ - A2 (only the first request slow): `reconnecting, recovered` → `waiting_first_token, reconnecting, recovered`.
11
+ - B′ (header window exactly 30 s): the same.
12
+ - B (headers 45 s, first-byte 30 s + 3 s upload allowance), C (the 600 s default) and D (40 s): unchanged.
13
+ - **Who is hurt.** A deployment whose first-token (or first-byte) window is exactly 30 s sees one more frame per attempt, at the deadline. The frame is closed by `recovered` / `gave_up` as before. client-core maps it to `kind: 'waiting'` and cli renders its waiting byline. A probe that counted zero wait frames under a 30 s watchdog goes red. The engine defaults are unchanged.
14
+ - Pins: `test/backlog746-first-token-watchdog-phase.test.ts`:
15
+ - A ① every attempt's wait is disclosed at its deadline; A ② control: the retries and the end; A ③ an event loop stalled across the deadline still gets exactly one wait frame before the retry.
16
+ - A2, B′ (the full trails), B, C, D (controls).
17
+ - Baseline v7.35.1: A ①, A ③, A2 and B′ red; A ②, B, C and D green.
18
+ - Unchanged and green: `backlog530-waiting-first-token`, including the 120 s cell and the stalled-event-loop cell.
19
+ - Mutations:
20
+ - D960, the disclosure arms only above the threshold (v7.35.1's `>`): A ①, A ③, A2 and B′ go red.
21
+ - D960b, the deadline does not disclose (`expire` does what `stop` does): the same four go red. Per site: the first-token watchdog calling `stop` turns A ①, A ③ and A2 red; the header `finally` always stopping turns B′ red.
22
+ - D960c, the deadline discloses a wait that was already disclosed: the controls go red (D, B, and `backlog530` ④ / ⑥ / ④c).
23
+ - Per-fixture table: `.gate-logs/criteria-7.35.2-mutations.md`.
24
+
25
+ ## 7.35.1 — 2026-09-29
26
+
27
+ ### A run that named no session holds the session its store minted — a second run on that id waits for it (#1206; behavior (tighten: a second run of a new session queues); +0 exports, +0 wire keys; +1 engine-invariant code `internal.session_lock_second_id` that no engine path reaches) (@server @test)
28
+
29
+ - **Before (v7.35.0).** A run that named its session (`TaskSpec.sessionId`) took the Runner's session lock before its prepare; a run that named none took no lock. Its session store minted the id inside prepare, and the id reached other callers through the run's tool contexts and events, so a second run started on it ran beside the first. Run A (`bypassPermissions`, a new session) spawned a child and was still running, or was cancelled with its tool pending; run B (`plan`) started on A's session id reached its first tool at once, and the child's ceiling kept answering A's `bypassPermissions` while B ran (KL-1192-c). Not a regression: v7.34.0's child held A's controller for life.
30
+ - **Now (one lock rule).** Every run holds ONE session lock from the moment it knows the session id to the run's end, decided by one read of the spec. A session the spec names is locked before prepare (unchanged). For a spec that names none, prepare takes the lock the moment the store mints the id. It is the same lock, released at the same place. Asking a run's hold again for the same id returns the same hold; asking it for another id is refused by name (`internal.session_lock_second_id`, an engine invariant no engine path reaches) rather than taking a second lock over the first. A second run on a minted id queues behind the first exactly as it does behind a named session; once the first run stops, the second enters, and a child of the first then follows it (`plan` refuses the child's write). Runs on different sessions are not serialized. The child ceiling rule (#1192) is unchanged.
31
+ - **Timing.** Measured on a prepare refusal: a run that named no session settles four microtasks later (46 → 50: the lock's await on the prepare path); a run naming its session reaches prepare two microtasks later (56 → 58: the hold's own step before the lock).
32
+ - **Who is hurt.** A caller that starts a second run on a new session's id while the first still runs sees that run start after the first ends: the order changes, not the outcome. The exception is a caller tool that starts a run on its OWN run's session id and awaits it: that now waits forever, as it already did for a run that named its session (no engine path does this). A probe that pinned the old overlap (two runs of one session side by side because the first named no session) goes red; rewrite it to expect the second run to queue, as the re-pinned cells below do. The lock is per Runner instance, so two Runners over one session store are not serialized, as for a named session. A caller that reads the freshly minted id off the store (`TtlSessionStore.list()`) and starts a run on it before the first run's prepare took the lock runs first; the first then queues behind it — the order is reversed and nothing waits forever (KL-1206).
33
+ - Pins: `test/backlog1206-new-session-lock.test.ts` ①–⑧, on a real Runner:
34
+ - ① / ② the second run queues behind a new-session run that was cancelled with its tool pending, or is still running. The reading is the named-session one, and the child then follows the second run's `plan`.
35
+ - ③ / ④ controls.
36
+ - ⑤ the lock is released on a prepare throw after the mint and on an abort.
37
+ - ⑥ tripwire for the retired shape: the second run's prepare does not begin while the first run runs, however the first got its session.
38
+ - ⑦ / ⑧ one decision per run: a `sessionId` inherited through the prototype chain, which the plan-mode rebind drops, with the run refused after prepare acquired, releases the named session's lock (the next run on it enters); a caller clearing its `sessionId` while the store's acquire is pending does not wait on its own lock and runs on the session it acquired. v7.35.0 green; the first cut of this fix red (it held twice and overwrote the first release, or waited on its own lock).
39
+
40
+ `test/backlog1206-session-lock-one-hold.test.ts` ⑨ / ⑩ (the lane's hold verb reached where prepare receives it): the same id again is the same hold, another id is refused by `internal.session_lock_second_id` and the first lock is still released at the run's end; a spec naming its session hands prepare no hold verb.
41
+
42
+ Re-pinned where the old overlap was the setup:
43
+ - `backlog1192-session-mode-seat` C1 / C3: the second run queues, and the child keeps the running leg's `plan`; `wrote` is 0 as before.
44
+ - `backlog744-notification-session-routing`, the two overlapping-run cells: the second run waits, the frame the idle first run cannot take parks, and a same-key retry folds on the producer's lane. The waiting run's turn-open carries it once.
45
+ - `design393-s6-run-leg-lanes`: rejection depth 46 → 50, because the lock's await is on the prepare path.
46
+
47
+ Mutations:
48
+ - D950, a new session is not locked: ①, ②, ⑥, C1, C3 and the rejection depth go red.
49
+ - D951, the minted lock is never released: ①, ②, ③, ⑤, ⑥, ⑨ go red.
50
+ - D952, the lock is released only when the run body resolves: ⑤ and ⑦ go red.
51
+ - D953, every minted session locks one shared key: control ④ goes red.
52
+ - D955, the hold keeps no owner: ⑨ goes red.
53
+ - D956, prepare is handed the hold for a named session too: ⑦ and ⑩ go red.
54
+ - The first cut of this fix (a second decision in prepare): ⑦, ⑧, ⑨ and ⑩ go red.
55
+
56
+ Known limits: KL-1192-c closed; KL-1206 (the mint-to-lock window).
57
+
1
58
  ## 7.35.0 — 2026-09-29
2
59
 
3
60
  **Hard-breaking changes — the whole release in three sentences.**
@@ -96,7 +96,8 @@ export declare function shouldRetryHeaderVerdict(res: Response | undefined): boo
96
96
  * {@link WAITING_FIRST_TOKEN_AFTER_MS}, and one more every {@link WAITING_FIRST_TOKEN_EVERY_MS} after
97
97
  * that, until the first token lands, the watchdog fires, or the attempt ends. A short wait (under the
98
98
  * threshold) announces nothing — the frame exists for the unattended "no answer for a minute" case,
99
- * not for every request. The same 30s slice the retry countdown re-announces on.
99
+ * not for every request. A wait whose watchdog fires AT the threshold is disclosed once, at that
100
+ * instant, before the cut is announced. The same 30s slice the retry countdown re-announces on.
100
101
  */
101
102
  export declare const WAITING_FIRST_TOKEN_AFTER_MS = 30000;
102
103
  export declare const WAITING_FIRST_TOKEN_EVERY_MS = 30000;
@@ -337,29 +337,40 @@ export function runStreamingBrain(args) {
337
337
  const idleTimeoutMs = resolveWatchdogMs(config.idleTimeoutMs, stallTimeouts?.idleMs, STALL_IDLE_MS, "idleTimeoutMs");
338
338
  const firstByteRetryBudget = resolveFirstByteRetries(config.firstByteRetries);
339
339
  const armWaitDisclosure = (ceilingMs) => {
340
- if (!(ceilingMs > WAITING_FIRST_TOKEN_AFTER_MS))
341
- return { stop: () => undefined };
340
+ if (!(ceilingMs >= WAITING_FIRST_TOKEN_AFTER_MS))
341
+ return { stop: () => undefined, expire: () => undefined };
342
342
  const startedAt = Date.now();
343
343
  const emitWaitFrame = bindBrainStatusEmitter();
344
344
  let timer;
345
345
  let stopped = false;
346
+ let disclosed = false;
347
+ const disclose = () => {
348
+ disclosed = true;
349
+ announcedWait = true;
350
+ emitWaitFrame({ phase: "waiting_first_token", detail: "waiting for the first token", elapsedMs: Date.now() - startedAt, timeoutMs: ceilingMs });
351
+ };
346
352
  const schedule = (delayMs) => {
347
353
  timer = setTimeout(() => {
348
354
  timer = undefined;
349
355
  if (stopped || Date.now() - startedAt >= ceilingMs)
350
356
  return;
351
- announcedWait = true;
352
- emitWaitFrame({ phase: "waiting_first_token", detail: "waiting for the first token", elapsedMs: Date.now() - startedAt, timeoutMs: ceilingMs });
357
+ disclose();
353
358
  schedule(WAITING_FIRST_TOKEN_EVERY_MS);
354
359
  }, delayMs);
355
360
  };
356
361
  schedule(WAITING_FIRST_TOKEN_AFTER_MS);
362
+ const stop = () => {
363
+ stopped = true;
364
+ if (timer)
365
+ clearTimeout(timer);
366
+ timer = undefined;
367
+ };
357
368
  return {
358
- stop: () => {
359
- stopped = true;
360
- if (timer)
361
- clearTimeout(timer);
362
- timer = undefined;
369
+ stop,
370
+ expire: () => {
371
+ if (!stopped && !disclosed)
372
+ disclose();
373
+ stop();
363
374
  },
364
375
  };
365
376
  };
@@ -378,13 +389,13 @@ export function runStreamingBrain(args) {
378
389
  throw e;
379
390
  }
380
391
  const window = firstByteWindow(firstByteTimeoutMs, Buffer.byteLength(req.body), firstByteStalls);
392
+ const headerWait = armWaitDisclosure(window.windowMs);
381
393
  const cc = createAttemptController(window.windowMs, signal);
382
394
  let r;
383
395
  let netErr;
384
396
  let firstByteStall;
385
397
  requestIdSeen = undefined;
386
398
  attemptPartial = undefined;
387
- const headerWait = armWaitDisclosure(window.windowMs);
388
399
  const stalledAtFirstByte = () => {
389
400
  firstByteWindowsWaited.push(window.windowMs);
390
401
  return {
@@ -423,7 +434,10 @@ export function runStreamingBrain(args) {
423
434
  }
424
435
  }
425
436
  finally {
426
- headerWait.stop();
437
+ if (firstByteStall !== undefined)
438
+ headerWait.expire();
439
+ else
440
+ headerWait.stop();
427
441
  }
428
442
  if (r && r.ok && r.body) {
429
443
  cc.clearTimer();
@@ -587,14 +601,14 @@ export function runStreamingBrain(args) {
587
601
  let firstTokenSeen = false;
588
602
  let firedFirstTokenDeadlineMs;
589
603
  let ftTimer;
590
- let firstTokenWait = { stop: () => undefined };
604
+ let firstTokenWait = { stop: () => undefined, expire: () => undefined };
591
605
  if (firstTokenTimeoutMs && firstTokenTimeoutMs > 0) {
606
+ firstTokenWait = armWaitDisclosure(firstTokenTimeoutMs);
592
607
  ftTimer = setTimeout(() => {
593
608
  firedFirstTokenDeadlineMs = firstTokenTimeoutMs;
594
- firstTokenWait.stop();
609
+ firstTokenWait.expire();
595
610
  void reader.cancel().catch(() => undefined);
596
611
  }, firstTokenTimeoutMs);
597
- firstTokenWait = armWaitDisclosure(firstTokenTimeoutMs);
598
612
  }
599
613
  let firedIdleDeadlineMs;
600
614
  let idleTimer;
@@ -2771,7 +2771,7 @@ export interface RunnerSelfSeat {
2771
2771
  */
2772
2772
  export type PrepareTaskFn = (spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, internals?: RunInternals, runnerSelf?: RunnerSelfSeat, runIdSink?: {
2773
2773
  runId?: string;
2774
- }) => Promise<Prepared>;
2774
+ }, holdSessionLock?: (sessionId: string) => Promise<void>) => Promise<Prepared>;
2775
2775
  /**
2776
2776
  * The Result of an INSTALLING phase: a lane whose every product is installed on a seat it borrowed
2777
2777
  * (the harness's stop gate and recovery chain, the run state's counter groups, the stream's result setter, the claimed
@@ -113,7 +113,9 @@ runnerSelf?: RunnerSelfSeat,
113
113
  * shape the Runner uses for the generated taskId/sessionId. */
114
114
  runIdSink?: {
115
115
  runId?: string;
116
- }): Promise<Prepared>;
116
+ },
117
+ /** The Runner's lock on this leg's session, handed only when the spec named none: taken the moment the store mints the id. */
118
+ holdSessionLock?: (sessionId: string) => Promise<void>): Promise<Prepared>;
117
119
  /** The entry IS the contract the notification lane's seat names ({@link PrepareTaskFn}, contracts.ts):
118
120
  * a signature change reds HERE, at the declaration, as `false` failing the `true` bound. IDENTITY, not assignability:
119
121
  * the parameter tuple (arity, optionality, each type) and the return type must be the SAME type in both directions —
@@ -282,7 +282,7 @@ function refusePermissionModeDoors(spec) {
282
282
  }
283
283
  return mode;
284
284
  }
285
- export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink) {
285
+ export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink, holdSessionLock) {
286
286
  const runId = uuidv7();
287
287
  if (runIdSink !== undefined)
288
288
  runIdSink.runId = runId;
@@ -308,6 +308,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
308
308
  const fileHistoryRequest = resolveFileHistoryRequest(spec, deps);
309
309
  const { acquired, session, conflictRef, wakeRecovered } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(internals?.sessionPlacement !== undefined ? { placement: internals.sessionPlacement } : {}), ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
310
310
  const sessionId = acquired.sessionId;
311
+ await holdSessionLock?.(sessionId);
311
312
  const onceLedger = await loadAnnounceOnceLedger(session, resume?.seed.announcedListings, deps.onError);
312
313
  const hostTaskId = spec.taskId ?? sessionId;
313
314
  const delegation = effectiveDelegationFacts(internals, resume?.seed.isDelegatedChild);
@@ -17,6 +17,8 @@ export interface RunNotificationLaneInput<R> {
17
17
  internals: RunInternals | undefined;
18
18
  /** borrowed-mutable — the stream layer's backstop carrier, handed to prepare so a prepare THROW still names its run. */
19
19
  taskIdRef: TaskIdRef | undefined;
20
+ /** borrowed-readonly — the stream layer's session-lock verb, handed to prepare; absent when the spec named its session. */
21
+ holdSessionLock: ((sessionId: string) => Promise<void>) | undefined;
20
22
  /** borrowed-readonly — the Runner's deployment deps, read LIVE (`onError` in the park closure — after the run, on a swapped
21
23
  * catalog it is still the CURRENT sink; `models` and prepare's `deps` at the natural-snapshot pin); the one home of why it
22
24
  * is a seat and not a captured object is {@link RunnerDepsSeat}. */
@@ -8,7 +8,7 @@ export function runNotificationLane(input) {
8
8
  return obligations.run(() => runNotificationLaneAs(input, obligations));
9
9
  }
10
10
  async function runNotificationLaneAs(input, obligations) {
11
- const { spec, queue, resume, internals, taskIdRef, runner, self, sessions, sessionReadStates, sessionModeSeats, pendingSessionNotifications, prepareTask, next } = input;
11
+ const { spec, queue, resume, internals, taskIdRef, holdSessionLock, runner, self, sessions, sessionReadStates, sessionModeSeats, pendingSessionNotifications, prepareTask, next } = input;
12
12
  const prepareResume = resume
13
13
  ? {
14
14
  leafId: resume.cp.leafId,
@@ -127,7 +127,7 @@ async function runNotificationLaneAs(input, obligations) {
127
127
  }
128
128
  return injectTaskNotification(notification, opts);
129
129
  },
130
- }, self, taskIdRef);
130
+ }, self, taskIdRef, holdSessionLock);
131
131
  pendingSessionNotifications.bindSessionBox(prepared.sessionId, prepared.sessionBox);
132
132
  const notificationLane = {
133
133
  get harness() { return notificationHarness; }, set harness(h) { notificationHarness = h; },
@@ -94,7 +94,8 @@ export declare class Runner implements RunnerSelfSeat {
94
94
  toolPolicy?: RunnerDeps["toolPolicy"];
95
95
  hooks?: RunnerDeps["hooks"];
96
96
  };
97
- /** Acquire the lock for a sessionId; returns a release fn. New (undefined) sessions need no lock. */
97
+ /** Acquire the lock for a sessionId; returns a release fn. The one lock every leg holds on its session, from the moment it
98
+ * knows the id to the leg's end (through `holdSessionLock` in `runTaskStream`, one per leg). */
98
99
  private acquireSessionLock;
99
100
  /**
100
101
  * Hot-swap the deployment seats a running Runner may change without a restart — the ONE door for every
@@ -330,14 +330,24 @@ export class Runner {
330
330
  };
331
331
  const runStartedAt = Date.now();
332
332
  const taskIdRef = { current: spec.taskId, sessionId: spec.sessionId };
333
+ let held;
334
+ let releaseLock;
335
+ const holdSessionLock = (sessionId) => {
336
+ if (held === undefined)
337
+ held = { sessionId, taken: this.acquireSessionLock(sessionId).then((release) => void (releaseLock = release)) };
338
+ else if (held.sessionId !== sessionId)
339
+ return Promise.reject(Object.assign(new Error(`internal invariant: a leg holds one session lock — it holds "${held.sessionId}" and was asked for "${sessionId}"`), { code: "internal.session_lock_second_id" }));
340
+ return held.taken;
341
+ };
342
+ const namedSessionId = spec.sessionId;
333
343
  const run = (async () => {
334
- const releaseLock = spec.sessionId
335
- ? await this.acquireSessionLock(spec.sessionId)
336
- : undefined;
344
+ if (namedSessionId)
345
+ await holdSessionLock(namedSessionId);
337
346
  try {
338
347
  await this.runLocked({
339
348
  spec,
340
349
  queue,
350
+ holdSessionLock: namedSessionId ? undefined : holdSessionLock,
341
351
  setResult: (r) => {
342
352
  resultValue = r;
343
353
  const pcs = internals?.inheritedGate?.parentConstraints;
@@ -436,9 +446,9 @@ export class Runner {
436
446
  };
437
447
  }
438
448
  runLocked(input) {
439
- const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, resume, internals, notifyRef, captureOptOutRef, mcpRedialRef, entryActor, entryTracer } = input;
449
+ const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, holdSessionLock, resume, internals, notifyRef, captureOptOutRef, mcpRedialRef, entryActor, entryTracer } = input;
440
450
  return runNotificationLane({
441
- spec, queue, resume, internals, taskIdRef,
451
+ spec, queue, resume, internals, taskIdRef, holdSessionLock,
442
452
  runner: this.depsSeat, self: this, sessions: this.sessions, sessionReadStates: this.sessionReadStates, sessionModeSeats: this.sessionModeSeats, pendingSessionNotifications: this.pendingSessionNotifications,
443
453
  prepareTask,
444
454
  next: ({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications }) => {
@@ -78,7 +78,8 @@ export type BrainStatusPhase = "rate_limited" | "retrying" | "reconnecting" | "c
78
78
  * The first-token WAIT, before any failure: a first-token watchdog is armed
79
79
  * (`firstTokenTimeoutMs > 0`) and nothing has arrived yet. Emitted once the wait has lasted
80
80
  * `WAITING_FIRST_TOKEN_AFTER_MS` and re-emitted every `WAITING_FIRST_TOKEN_EVERY_MS` while it goes
81
- * on, carrying {@link BrainStatus.elapsedMs} and {@link BrainStatus.timeoutMs}; never carries a
81
+ * on; a wait whose watchdog fires at the threshold itself is emitted once at that instant, before the
82
+ * frame announcing the cut. Carries {@link BrainStatus.elapsedMs} and {@link BrainStatus.timeoutMs}; never carries a
82
83
  * retry countdown (`retryInMs`/`retryAtMs` — no wait is being announced, a wait is being
83
84
  * OBSERVED). CLOSED like a retry trail: once a wait frame went out, the call's terminal frame
84
85
  * follows — `recovered` when the call went on to be served (the first token arrived, on this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.35.0",
3
+ "version": "7.35.2",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -0,0 +1,59 @@
1
+ {
2
+ "repo": "core",
3
+ "version": "7.35.1",
4
+ "from": "b0a5a6ddd0b430433d12452260aa51cf96f05972",
5
+ "to": "v7.35.1",
6
+ "added": [],
7
+ "removed": [],
8
+ "changed": [],
9
+ "narrowed": [],
10
+ "closedSetMembers": [],
11
+ "closedSetsUnreadable": [],
12
+ "typeKeys": [],
13
+ "typeKeysUnreadableArms": [],
14
+ "typeKeysFlattenedCount": 219,
15
+ "declarationMoves": [],
16
+ "mintMoves": [],
17
+ "criteriaObserves": [
18
+ {
19
+ "kind": "typeMember",
20
+ "name": "SessionStore.acquire",
21
+ "assertions": [
22
+ "G1.c"
23
+ ]
24
+ },
25
+ {
26
+ "kind": "typeMember",
27
+ "name": "TaskResult.sessionId",
28
+ "assertions": [
29
+ "G1.b"
30
+ ]
31
+ },
32
+ {
33
+ "kind": "typeMember",
34
+ "name": "TaskSpec.permissionMode",
35
+ "assertions": [
36
+ "G1.a",
37
+ "G1.d"
38
+ ]
39
+ },
40
+ {
41
+ "kind": "typeMember",
42
+ "name": "TaskSpec.sessionId",
43
+ "assertions": [
44
+ "G1.a",
45
+ "G1.c",
46
+ "G1.d"
47
+ ]
48
+ }
49
+ ],
50
+ "consumersHint": {
51
+ "cli": [],
52
+ "server": [],
53
+ "sdk": [],
54
+ "note": "scanned cli@6b6025f7 server@25d6b7a6 sdk@e8f6f0d — a name listed is MENTIONED there today, which is evidence of consumption, not proof of it"
55
+ },
56
+ "breakingCount": 0,
57
+ "breakingItems": [],
58
+ "toResolvedAt": "27a9b09aced4d24b63a68ca78f4186c6fd5fa944"
59
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "repo": "core",
3
+ "version": "7.35.2",
4
+ "from": "47ed594ee8d729368c4be8d8a189d908b33e15e5",
5
+ "to": "v7.35.2",
6
+ "added": [],
7
+ "removed": [],
8
+ "changed": [],
9
+ "narrowed": [],
10
+ "closedSetMembers": [],
11
+ "closedSetsUnreadable": [],
12
+ "typeKeys": [],
13
+ "typeKeysUnreadableArms": [],
14
+ "typeKeysFlattenedCount": 219,
15
+ "declarationMoves": [],
16
+ "mintMoves": [],
17
+ "criteriaObserves": [
18
+ {
19
+ "kind": "typeMember",
20
+ "name": "BrainStatus.elapsedMs",
21
+ "assertions": [
22
+ "G1.a",
23
+ "G1.c"
24
+ ]
25
+ },
26
+ {
27
+ "kind": "typeMember",
28
+ "name": "BrainStatus.errClass",
29
+ "assertions": [
30
+ "G1.b"
31
+ ]
32
+ },
33
+ {
34
+ "kind": "typeMember",
35
+ "name": "BrainStatus.phase",
36
+ "assertions": [
37
+ "G1.a",
38
+ "G1.b",
39
+ "G1.c"
40
+ ]
41
+ },
42
+ {
43
+ "kind": "typeMember",
44
+ "name": "BrainStatus.timeoutMs",
45
+ "assertions": [
46
+ "G1.a",
47
+ "G1.b",
48
+ "G1.c"
49
+ ]
50
+ }
51
+ ],
52
+ "consumersHint": {
53
+ "cli": [],
54
+ "server": [],
55
+ "sdk": [],
56
+ "note": "scanned cli@6b6025f7 server@25d6b7a6 sdk@e8f6f0d — a name listed is MENTIONED there today, which is evidence of consumption, not proof of it"
57
+ },
58
+ "breakingCount": 0,
59
+ "breakingItems": [],
60
+ "toResolvedAt": "8a69688057ed91863f6574e06b9f4f83dfdcb7a6"
61
+ }