@ai-dossier/sched 0.34.0 → 0.36.0

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/README.md CHANGED
@@ -307,6 +307,12 @@ profiles exist and detection is inconclusive, enqueue fails and names the availa
307
307
  profiles rather than silently choosing the default. `--dispatch` is intentionally rejected
308
308
  for full-cycle entries; no profiles means legacy dispatch behavior remains unchanged.
309
309
 
310
+ Host defaults live at top-level `dispatch_profiles` in `~/.dossier/config.json`.
311
+ Scheduler projects inherit those profiles even before their own config exists. A
312
+ project's `dispatch.dispatch_profiles` overrides matching names only, leaving other
313
+ host profiles available. `sched status` reports every effective profile's source as
314
+ `user` or `project`.
315
+
310
316
  Two engine-safety policies were explicit product decisions on #464:
311
317
 
312
318
  - **Pid identity is hybrid-verified (decision 1, option C).** Every spawn records the
@@ -1 +1 @@
1
- {"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"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"}
@@ -1807,6 +1807,11 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
1807
1807
  const batch = (0, state_1.findBatch)(state, batchId);
1808
1808
  if (!batch)
1809
1809
  throw new types_1.SchedNotFoundError(`Batch not found: ${batchId}`);
1810
+ // A gate recheck can immediately advance to the next provider dispatch, so
1811
+ // it must use the batch's recorded profile rather than the engine default.
1812
+ const batchDispatch = batch.dispatch_profile === null
1813
+ ? dispatch
1814
+ : (0, dispatch_1.resolveProfiledDispatch)(config, batch.dispatch_profile);
1810
1815
  if (batch.status !== 'blocked' || !batch.blocked_reason?.startsWith('gate-inconclusive:')) {
1811
1816
  // #677 review: the landing-failure blocks have no resume verb (same as
1812
1817
  // `suite-unreadable`) — name the actual reason and the operator path
@@ -1855,7 +1860,7 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
1855
1860
  outputTail: recheck.outputTail,
1856
1861
  durationMs: recheck.durationMs,
1857
1862
  }, now);
1858
- completeMemberGate(deps, config, dispatch, batchId, batch, memberIssue, now, result);
1863
+ completeMemberGate(deps, config, batchDispatch, batchId, batch, memberIssue, now, result);
1859
1864
  return { outcome: 'skipped', capability: capabilityId, detail: excerpt };
1860
1865
  }
1861
1866
  recordMemberGate(deps, batchId, memberIssue, { id: capabilityId, ...recheck }, now);
@@ -1903,7 +1908,7 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
1903
1908
  if (recheck.outcome === 'task-failed') {
1904
1909
  const reason = `incremental-gate-failed:${capabilityId}`;
1905
1910
  writeGateLog(deps, batchId, capabilityId, memberIssue, recheck.outputTail);
1906
- evictMemberAndContinue(deps, config, dispatch, batchId, batch, memberIssue, {
1911
+ evictMemberAndContinue(deps, config, batchDispatch, batchId, batch, memberIssue, {
1907
1912
  reason,
1908
1913
  detail: withExcerpt(`sched resume --batch: cap run ${capabilityId} reported task-failed on recheck`, excerpt),
1909
1914
  }, now, result);
@@ -1913,7 +1918,7 @@ function resumeBlockedGate(deps, config, dispatch, batchId, now) {
1913
1918
  issue: memberIssue,
1914
1919
  detail: `sched resume --batch: cap run ${capabilityId} reported ok on recheck`,
1915
1920
  });
1916
- completeMemberGate(deps, config, dispatch, batchId, batch, memberIssue, now, result);
1921
+ completeMemberGate(deps, config, batchDispatch, batchId, batch, memberIssue, now, result);
1917
1922
  return { outcome: 'completed', capability: capabilityId };
1918
1923
  }
1919
1924
  /**
@@ -2795,9 +2800,9 @@ function runBatchTick(deps, config, dispatch) {
2795
2800
  const result = emptyResult();
2796
2801
  const now = deps.now();
2797
2802
  // #707: each batch dispatches through ITS OWN recorded profile, not the
2798
- // engine's default. Resolution is cached per tick (per runBatchTick call):
2799
- // within one pass a profile resolves at most once, and a failed resolution
2800
- // reacts at most once. A profile that no longer resolves (config edited
2803
+ // engine's default. Successful resolution is cached per tick (per
2804
+ // runBatchTick call); missing profiles are handled per batch. A profile that
2805
+ // no longer resolves (config edited
2801
2806
  // mid-run) never silently falls back — the silent fallback is precisely
2802
2807
  // the #680 incident shape (a run that looked like one agent family,
2803
2808
  // executed as another). The reaction depends on how far the batch got:
@@ -2808,22 +2813,32 @@ function runBatchTick(deps, config, dispatch) {
2808
2813
  // - post-merge (PR parked/merged — the product already shipped): dissolving
2809
2814
  // would discard a landed PR's bookkeeping, so the remaining dispatch (the
2810
2815
  // report agent) runs on the default and journals
2811
- // `dispatch-profile-missing` every tick it holds the decision open — a
2812
- // bounded phase (deployed → reported), so the event cannot flood.
2816
+ // `dispatch-profile-missing` once while it holds the decision open — a
2817
+ // bounded phase (deployed → reported), with the journal acting as the
2818
+ // durable deduplication record across ticks and process restarts.
2813
2819
  const profileCache = new Map();
2820
+ const missingProfileBatches = new Set();
2821
+ const missingProfileNotices = new Set(deps.journal
2822
+ .read()
2823
+ .filter((event) => event.event === 'dispatch-profile-missing' && event.unit !== undefined)
2824
+ .map((event) => event.unit));
2814
2825
  const dispatchFor = (batch) => {
2815
2826
  if (batch.dispatch_profile === null)
2816
2827
  return dispatch;
2817
2828
  const cached = profileCache.get(batch.dispatch_profile);
2818
2829
  if (cached !== undefined)
2819
2830
  return cached;
2820
- let resolved;
2831
+ if (missingProfileBatches.has(batch.id))
2832
+ return dispatch;
2821
2833
  try {
2822
- resolved = (0, dispatch_1.resolveProfiledDispatch)(config, batch.dispatch_profile);
2834
+ const resolved = (0, dispatch_1.resolveProfiledDispatch)(config, batch.dispatch_profile);
2835
+ profileCache.set(batch.dispatch_profile, resolved);
2836
+ return resolved;
2823
2837
  }
2824
2838
  catch (err) {
2825
2839
  if (!(err instanceof dispatch_1.DispatchProfileError))
2826
2840
  throw err;
2841
+ missingProfileBatches.add(batch.id);
2827
2842
  const current = deps.store.load();
2828
2843
  const fresh = (0, state_1.findBatch)(current, batch.id);
2829
2844
  const dissolvable = fresh !== undefined && (0, state_1.allowedBatchTransitions)(fresh.status).includes('dissolving');
@@ -2839,21 +2854,25 @@ function runBatchTick(deps, config, dispatch) {
2839
2854
  state: applyBatchAndIssues(s, outcome.state, batch.id, outcome.requeued),
2840
2855
  result: undefined,
2841
2856
  }));
2857
+ teardownBatch(deps, batch.id);
2842
2858
  result.failed.push(unit(batch.id));
2843
- // Resolution failed: skip this batch for the rest of the pass with the
2844
- // base dispatch as a harmless placeholder — the batch is dissolved, so
2845
- // no further arm can claim a slot for it this tick.
2846
- resolved = dispatch;
2859
+ // Resolution failed: clean up any batch/member worktrees, then skip
2860
+ // this batch for the rest of the pass. No further arm can claim it.
2861
+ // Do not cache a missing profile: every batch must receive its own
2862
+ // dissolve/journal reaction rather than inheriting a cached default.
2863
+ return null;
2847
2864
  }
2848
2865
  else {
2849
- journalEvent(deps, 'dispatch-profile-missing', unit(batch.id), {
2850
- detail: `profile '${batch.dispatch_profile}' is no longer configured; the post-merge tail runs on the default dispatch`,
2851
- });
2852
- resolved = dispatch;
2866
+ const batchUnit = unit(batch.id);
2867
+ if (!missingProfileNotices.has(batchUnit)) {
2868
+ journalEvent(deps, 'dispatch-profile-missing', batchUnit, {
2869
+ detail: `profile '${batch.dispatch_profile}' is no longer configured; the post-merge tail runs on the default dispatch`,
2870
+ });
2871
+ missingProfileNotices.add(batchUnit);
2872
+ }
2873
+ return dispatch;
2853
2874
  }
2854
2875
  }
2855
- profileCache.set(batch.dispatch_profile, resolved);
2856
- return resolved;
2857
2876
  };
2858
2877
  // #565: when more ready batches exist than free capacity, claim them in
2859
2878
  // the same priority order `runnableUnits` would (desc priority → asc
@@ -2888,13 +2907,13 @@ function runBatchTick(deps, config, dispatch) {
2888
2907
  if (batch && batch.status === 'ready' && slotFor(state, batchId) === undefined) {
2889
2908
  // #707: batch-setup dispatches through the batch's own profile too — it
2890
2909
  // is the batch's first agent, not an engine-internal step.
2891
- claimAndSetup(deps, config, dispatchFor(batch), batchId, now, result);
2910
+ const batchDispatch = dispatchFor(batch);
2911
+ if (batchDispatch !== null) {
2912
+ claimAndSetup(deps, config, batchDispatch, batchId, now, result);
2913
+ }
2892
2914
  }
2893
2915
  }
2894
2916
  for (const batch of deps.store.load().batches) {
2895
- // #707: resolve this batch's profile once per pass — or dissolve/fall
2896
- // back loudly when it no longer resolves (see `dispatchFor` above).
2897
- const batchDispatch = dispatchFor(batch);
2898
2917
  const slot = slotFor(deps.store.load(), batch.id);
2899
2918
  // #609: a TERMINAL batch (`done`/`dissolved`) still holding a slot matches
2900
2919
  // none of the status arms below and would fall through their `continue`,
@@ -2906,17 +2925,26 @@ function runBatchTick(deps, config, dispatch) {
2906
2925
  // `abandonBatch` that stops one from being created: it also REPAIRS state
2907
2926
  // files that already carry a leaked slot, which is the only way an
2908
2927
  // operator gets those three hours of held capacity back.
2909
- if (slot && types_1.TERMINAL_BATCH_STATUSES.has(batch.status)) {
2910
- deps.store.withLock((s) => ({
2911
- state: (0, state_1.releaseBatchSlot)(s, batch.id, now),
2912
- result: undefined,
2913
- }));
2914
- journalEvent(deps, 'slot-released', unit(batch.id), {
2915
- slot: slot.id,
2916
- detail: `terminal batch (${batch.status}) held slot ${slot.id}`,
2917
- });
2928
+ if (types_1.TERMINAL_BATCH_STATUSES.has(batch.status)) {
2929
+ if (slot) {
2930
+ deps.store.withLock((s) => ({
2931
+ state: (0, state_1.releaseBatchSlot)(s, batch.id, now),
2932
+ result: undefined,
2933
+ }));
2934
+ journalEvent(deps, 'slot-released', unit(batch.id), {
2935
+ slot: slot.id,
2936
+ detail: `terminal batch (${batch.status}) held slot ${slot.id}`,
2937
+ });
2938
+ }
2918
2939
  continue;
2919
2940
  }
2941
+ // #707: resolve this batch's profile once per pass — or dissolve/fall
2942
+ // back loudly when it no longer resolves (see `dispatchFor` above).
2943
+ // Terminal batches are skipped before this call so a removed profile does
2944
+ // not generate a missing-profile event on every later tick.
2945
+ const batchDispatch = dispatchFor(batch);
2946
+ if (batchDispatch === null)
2947
+ continue;
2920
2948
  if (slot && (slot.status === 'running' || slot.status === 'assigned')) {
2921
2949
  if (batch.status === 'executing') {
2922
2950
  reconcileMemberSlot(deps, config, batchDispatch, batch.id, slot, now, result);