@volter/supercode-orchestrator 0.5.39 → 0.5.40

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.
@@ -2237,7 +2237,11 @@ async function notify(board, fleet, report, entry) {
2237
2237
  // and answers, each its own message, in full. A notice the board already recorded as taken by this address is never sent again.
2238
2238
  // only events that would be told count: a stale one or the recipient's own act never supersedes one that would be
2239
2239
  const sendable = (ev) => { const p = json(ev.payload, {}) ?? {}; return !(maxAge && now() - Number(ev.created_at) > maxAge) && !(p.by && p.by === sub.chat_id); };
2240
- const latest = new Map(events.filter((ev) => !['commented', 'answered'].includes(ev.kind) && sendable(ev)).map((ev) => [ev.kind, ev.id]));
2240
+ // News is coalesced by its subject, not its kind alone: a later `assigned` says what an earlier one did, but each
2241
+ // subtask added, dependency linked or unlinked is its own fact, keyed by the card it names; a comment or an
2242
+ // answer is always its own message.
2243
+ const subjectOf = (ev) => { const p = json(ev.payload, {}) ?? {}; return ['commented', 'answered'].includes(ev.kind) ? `${ev.kind}:${ev.id}` : `${ev.kind}:${p.subtask ?? p.parent ?? p.child ?? ''}`; };
2244
+ const latest = new Map(events.filter((ev) => sendable(ev)).map((ev) => [subjectOf(ev), ev.id]));
2241
2245
  const sentBefore = board.db.prepare("SELECT 1 FROM task_events WHERE task_id = ? AND kind IN ('notice_delivered', 'notice_filed') AND json_extract(payload, '$.source_event') = ? AND json_extract(payload, '$.address') = ? LIMIT 1");
2242
2246
  for (const ev of events) {
2243
2247
  const p = json(ev.payload, {}) ?? {};
@@ -2246,7 +2250,7 @@ async function notify(board, fleet, report, entry) {
2246
2250
  if (sentBefore.get(sub.task_id, ev.id, sub.chat_id)) { cursor = ev.id; continue; }
2247
2251
  // nobody is told of their own act
2248
2252
  if (p.by && p.by === sub.chat_id) { cursor = ev.id; continue; }
2249
- if (latest.has(ev.kind) && latest.get(ev.kind) !== ev.id) { cursor = ev.id; (report.superseded_notices_passed ??= 0); report.superseded_notices_passed += 1; continue; }
2253
+ if (latest.has(subjectOf(ev)) && latest.get(subjectOf(ev)) !== ev.id) { cursor = ev.id; (report.superseded_notices_passed ??= 0); report.superseded_notices_passed += 1; continue; }
2250
2254
  let text;
2251
2255
  if (ev.kind === 'commented') {
2252
2256
  if (p.author === sub.chat_id) { cursor = ev.id; continue; }
@@ -2305,13 +2309,14 @@ async function notify(board, fleet, report, entry) {
2305
2309
  // attempt it is abandoned (its terminal state, `notice_abandoned`, recorded once) and the cursor moves past it;
2306
2310
  // the subscription stays, so the notices after it are still told (dropping it lost them when the round
2307
2311
  // subscribed the session again from the card's newest event).
2308
- const failures = failed ? Number(sub.delivery_metadata?.failures ?? 0) + 1 : 0;
2312
+ // failures are counted per notice: a failure of a later notice starts its own count
2313
+ const failures = failed ? (Number(sub.delivery_metadata?.failing_event) === trying ? Number(sub.delivery_metadata?.failures ?? 0) : 0) + 1 : 0;
2309
2314
  if (failed && failures >= NOTICE_ATTEMPTS && trying) {
2310
2315
  event(db, sub.task_id, 'notice_abandoned', { source_event: trying, address: sub.chat_id, attempts: failures, error: failed });
2311
2316
  db.prepare(`UPDATE kanban_notify_subs SET last_event_id = ?, delivery_metadata = ? ${where} AND last_event_id < ?`).run(trying, JSON.stringify({ ...sub.delivery_metadata, failures: 0, next_at: 0 }), ...identity, trying);
2312
2317
  return;
2313
2318
  }
2314
- if (failures !== Number(sub.delivery_metadata?.failures ?? 0)) db.prepare(`UPDATE kanban_notify_subs SET delivery_metadata = ? ${where}`).run(JSON.stringify({ ...sub.delivery_metadata, failures, next_at: failed ? now() + Math.min(1800, 30 * 2 ** (failures - 1)) : 0 }), ...identity);
2319
+ if (failures !== Number(sub.delivery_metadata?.failures ?? 0) || (failed && Number(sub.delivery_metadata?.failing_event) !== trying)) db.prepare(`UPDATE kanban_notify_subs SET delivery_metadata = ? ${where}`).run(JSON.stringify({ ...sub.delivery_metadata, failures, failing_event: failed ? trying : null, next_at: failed ? now() + Math.min(1800, 30 * 2 ** (failures - 1)) : 0 }), ...identity);
2315
2320
  if (task?.status === 'archived' && cursor >= events.at(-1).id) db.prepare(`DELETE FROM kanban_notify_subs ${where}`).run(...identity);
2316
2321
  });
2317
2322
  });
package/board/engine.mjs CHANGED
@@ -974,6 +974,11 @@ export function diagnose(db, task, { failureLimit = DEFAULT_FAILURE_LIMIT, stran
974
974
  ? { kind: 'effect_failed', severity: 'warning', title: `its ${kind} failed for good after ${row.attempts} attempts: ${row.last_error ?? 'no reason recorded'}`, data: { effect: row.id, kind, attempts: row.attempts }, actions: [] }
975
975
  : { kind: 'effect_stalled', severity: 'error', title: `its ${kind} has failed ${row.attempts} times and is still tried: ${row.last_error ?? 'no reason recorded'}`, data: { effect: row.id, kind, attempts: row.attempts }, actions: [] });
976
976
  }
977
+ // notices the board gave up on: each one, with the event it was about and who was not told
978
+ for (const row of all(db, "SELECT payload FROM task_events WHERE task_id = ? AND kind = 'notice_abandoned' ORDER BY id DESC LIMIT 20", task.id)) {
979
+ const p = json(row.payload, {}) ?? {};
980
+ out.push({ kind: 'notice_abandoned', severity: 'warning', title: `the notice of event ${p.source_event} to ${p.address} was abandoned after ${p.attempts} attempts: ${p.error ?? 'no reason recorded'}`, data: { event: p.source_event, address: p.address, attempts: p.attempts }, actions: [] });
981
+ }
977
982
  const order = ['critical', 'error', 'warning'];
978
983
  return out.sort((a, b) => order.indexOf(a.severity) - order.indexOf(b.severity));
979
984
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-orchestrator",
3
- "version": "0.5.39",
3
+ "version": "0.5.40",
4
4
  "type": "module",
5
5
  "description": "The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)",
6
6
  "exports": {