@adhdev/mesh-shared 1.0.60-rc.44 → 1.0.60-rc.46

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.
@@ -42,6 +42,16 @@ export declare const DELIVERY_VIAS: readonly ["local", "p2p"];
42
42
  export type DeliveryVia = typeof DELIVERY_VIAS[number];
43
43
  export declare const DISPATCH_FAILURE_REASONS: readonly ["worker_absent", "transport_error", "spawn_failed", "rejected_by_worker", "timeout"];
44
44
  export type DispatchFailureReason = typeof DISPATCH_FAILURE_REASONS[number];
45
+ /**
46
+ * Normalize a worker's own refusal code (e.g. `session_busy_with_task`,
47
+ * `mesh_sender_not_on_roster`, `provider_quota_exhausted`) into the shape
48
+ * `dispatch_failed.refusalCode` accepts: lowercase ascii + underscore, capped
49
+ * at 64 chars. Returns undefined for anything that yields no usable token, so
50
+ * a malformed/foreign code degrades to "no refusalCode" rather than smuggling
51
+ * arbitrary text through what must stay a closed-enum-like identifier slot
52
+ * (see the field's doc comment — this evidence replicates cross-machine).
53
+ */
54
+ export declare function sanitizeRefusalCode(value: unknown): string | undefined;
45
55
  export declare const CONSUME_PROFILES: readonly ["default", "native_source"];
46
56
  export type ConsumeProfile = typeof CONSUME_PROFILES[number];
47
57
  export declare const SESSION_REBOUND_REASONS: readonly ["restart", "live_holder"];
@@ -87,7 +97,7 @@ export type NoProgressObservedStatus = SessionStatus | 'unknown';
87
97
  * Reasons a turn hold exists. Holds are attempt-scoped only (C2: delivery-side
88
98
  * waits are consumer deferral on the `turn.deliver` cursor, not hold rows).
89
99
  */
90
- export declare const HOLD_REASONS: readonly ["await_delivery", "await_consume", "await_turn", "liveness", "hard_ceiling", "live_pending", "transcript_quiet", "weak_candidate", "suspension_before_consumed", "await_report"];
100
+ export declare const HOLD_REASONS: readonly ["await_delivery", "await_consume", "await_turn", "liveness", "hard_ceiling", "live_pending", "transcript_quiet", "weak_candidate", "suspension_before_consumed", "await_report", "await_end"];
91
101
  export type HoldReason = typeof HOLD_REASONS[number];
92
102
  export declare const TURN_OUTCOMES: readonly ["completed", "failed", "cancelled"];
93
103
  export type TurnOutcome = typeof TURN_OUTCOMES[number];
@@ -174,10 +184,25 @@ export type TurnEvidenceBody = {
174
184
  kind: 'delivery_refused';
175
185
  messageId: string;
176
186
  reason: SendRefusal;
177
- } | {
187
+ }
188
+ /**
189
+ * `refusalCode` (wiring-unification live-gap fix, 2026-09-25): the worker's own
190
+ * refusal code for an application-level `{success:false}` answer (e.g.
191
+ * `session_busy_with_task`, `mesh_sender_not_on_roster`,
192
+ * `mesh_node_bootstrap_pending`, `provider_quota_exhausted`) — optional and
193
+ * ONLY ever populated for `reason: 'rejected_by_worker'`. Deliberately an `id`
194
+ * (short enum-like token), never free text: this evidence replicates across
195
+ * machines over `mesh.<id>.events` (CONTENT-FREE BY CONSTRUCTION above), so a
196
+ * human-readable detail string must NOT travel here — it stays local (owner
197
+ * WARN log, MCP tool JSON response). Producers sanitize to `[a-z_]{1,64}`
198
+ * before emitting so a stray free-text code can never smuggle prose through
199
+ * this slot.
200
+ */
201
+ | {
178
202
  kind: 'dispatch_failed';
179
203
  workerAbsent: boolean;
180
204
  reason: DispatchFailureReason;
205
+ refusalCode?: string;
181
206
  } | {
182
207
  kind: 'duplicate_dispatch_refusal';
183
208
  holderSessionId: string;
@@ -240,10 +265,16 @@ export type TurnEvidenceBody = {
240
265
  } | {
241
266
  kind: 'session_error';
242
267
  reason: SessionErrorReason;
243
- } | {
268
+ }
269
+ /**
270
+ * `summary` is the handoff-topic pointer when the report's text was published;
271
+ * a report the owner daemon accepted itself carries its text in the evidence
272
+ * row's local envelope instead (never published), so the pointer is optional.
273
+ */
274
+ | {
244
275
  kind: 'worker_report';
245
276
  outcome: WorkerReportOutcome;
246
- summary: SummaryRef;
277
+ summary?: SummaryRef;
247
278
  hasHandoffNotes: boolean;
248
279
  branchState?: WorkerBranchState;
249
280
  touchedFileCount?: number;
@@ -334,7 +365,7 @@ export declare const isEvidenceSourceId: (value: unknown) => value is "fsm_edge"
334
365
  export declare const isTurnEvidenceKind: (value: unknown) => value is "no_progress" | "delivered" | "liveness" | "turn_end" | "transcript_final" | "session_error" | "dispatch_failed" | "dispatch_accepted" | "delivery_refused" | "duplicate_dispatch_refusal" | "session_rebound" | "turn_started" | "suspension" | "suspension_resolved" | "transcript_activity" | "process_exit" | "worker_report" | "worker_progress" | "git_side_effect" | "cancel" | "operator_status" | "coordinator_ack" | "hold_expired";
335
366
  export declare const isTurnReason: (value: unknown) => value is "intentional_cleanup" | "operator_cancel" | "task_removed" | "mission_abandoned" | "superseded" | "operator_update" | "refine_terminal" | "validation_terminal" | "hard_ceiling" | "turn_end" | "weak_end_confirmed" | "transcript_final" | "worker_reported" | "finalization_timeout_no_response" | "session_error" | "provider_auth_failed" | "provider_billing_failed" | "reclaim_budget_exhausted" | "hollow_max_retries" | "migration_orphan" | "dispatch_refused_session_exited" | "dispatch_refused_no_target" | "dispatch_refused_unsupported_input" | "dispatch_failed" | "session_exit" | "session_exit_before_turn" | "session_dead" | "hollow_completion" | "assigned_stranded_dispatch_unconfirmed" | "delivered_not_consumed_redrive" | "delivered_no_turn_deadline";
336
367
  export declare const isNotifyKind: (value: unknown) => value is "stopped" | "failed" | "no_progress" | "completed" | "cancelled" | "approval" | "choice" | "approval_resolved" | "candidate" | "progress" | "late_completion" | "mesh_event";
337
- export declare const isHoldReason: (value: unknown) => value is "await_delivery" | "await_consume" | "await_turn" | "liveness" | "hard_ceiling" | "live_pending" | "transcript_quiet" | "weak_candidate" | "suspension_before_consumed" | "await_report";
368
+ export declare const isHoldReason: (value: unknown) => value is "await_delivery" | "await_consume" | "await_turn" | "liveness" | "hard_ceiling" | "live_pending" | "transcript_quiet" | "weak_candidate" | "suspension_before_consumed" | "await_report" | "await_end";
338
369
  export declare const isTurnOutcome: (value: unknown) => value is "failed" | "completed" | "cancelled";
339
370
  export declare const isCommitStrength: (value: unknown) => value is "operator" | "genuine" | "weak" | "tool_report";
340
371
  export declare function isEvidenceIdentifier(value: unknown): value is string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/mesh-shared",
3
- "version": "1.0.60-rc.44",
3
+ "version": "1.0.60-rc.46",
4
4
  "description": "ADHDev mesh-shared — pure mesh/git status normalizers shared by daemon-core and web-core",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -59,6 +59,22 @@ export type DeliveryVia = typeof DELIVERY_VIAS[number]
59
59
  export const DISPATCH_FAILURE_REASONS = ['worker_absent', 'transport_error', 'spawn_failed', 'rejected_by_worker', 'timeout'] as const
60
60
  export type DispatchFailureReason = typeof DISPATCH_FAILURE_REASONS[number]
61
61
 
62
+ /**
63
+ * Normalize a worker's own refusal code (e.g. `session_busy_with_task`,
64
+ * `mesh_sender_not_on_roster`, `provider_quota_exhausted`) into the shape
65
+ * `dispatch_failed.refusalCode` accepts: lowercase ascii + underscore, capped
66
+ * at 64 chars. Returns undefined for anything that yields no usable token, so
67
+ * a malformed/foreign code degrades to "no refusalCode" rather than smuggling
68
+ * arbitrary text through what must stay a closed-enum-like identifier slot
69
+ * (see the field's doc comment — this evidence replicates cross-machine).
70
+ */
71
+ export function sanitizeRefusalCode(value: unknown): string | undefined {
72
+ if (typeof value !== 'string') return undefined
73
+ const normalized = value.trim().toLowerCase().replace(/[^a-z_]/g, '_').replace(/_+/g, '_').replace(/^_+|_+$/g, '')
74
+ if (!normalized) return undefined
75
+ return normalized.slice(0, 64)
76
+ }
77
+
62
78
  export const CONSUME_PROFILES = ['default', 'native_source'] as const
63
79
  export type ConsumeProfile = typeof CONSUME_PROFILES[number]
64
80
 
@@ -127,6 +143,12 @@ export const HOLD_REASONS = [
127
143
  // only opens this hold (R9r) — a report commits (R17), a new busy edge cancels
128
144
  // it as a false idle (R12r), expiry commits weak (R13r).
129
145
  'await_report',
146
+ // A worker report recorded while the session is still generating (R17g):
147
+ // the report is the verdict, the idle edge is only awaited as corroboration
148
+ // — a turn_end commits the report (R9t), expiry commits it anyway (R13t) so
149
+ // a session whose FSM never shows the idle edge again does not wait for
150
+ // liveness / hard_ceiling.
151
+ 'await_end',
130
152
  ] as const
131
153
  export type HoldReason = typeof HOLD_REASONS[number]
132
154
 
@@ -208,7 +230,20 @@ export type TurnEvidenceBody =
208
230
  coordinator?: EvidenceCoordinatorIdentity }
209
231
  | { kind: 'delivered'; messageId: string; outcome: DeliveryOutcome; via: DeliveryVia }
210
232
  | { kind: 'delivery_refused'; messageId: string; reason: SendRefusal }
211
- | { kind: 'dispatch_failed'; workerAbsent: boolean; reason: DispatchFailureReason }
233
+ /**
234
+ * `refusalCode` (wiring-unification live-gap fix, 2026-09-25): the worker's own
235
+ * refusal code for an application-level `{success:false}` answer (e.g.
236
+ * `session_busy_with_task`, `mesh_sender_not_on_roster`,
237
+ * `mesh_node_bootstrap_pending`, `provider_quota_exhausted`) — optional and
238
+ * ONLY ever populated for `reason: 'rejected_by_worker'`. Deliberately an `id`
239
+ * (short enum-like token), never free text: this evidence replicates across
240
+ * machines over `mesh.<id>.events` (CONTENT-FREE BY CONSTRUCTION above), so a
241
+ * human-readable detail string must NOT travel here — it stays local (owner
242
+ * WARN log, MCP tool JSON response). Producers sanitize to `[a-z_]{1,64}`
243
+ * before emitting so a stray free-text code can never smuggle prose through
244
+ * this slot.
245
+ */
246
+ | { kind: 'dispatch_failed'; workerAbsent: boolean; reason: DispatchFailureReason; refusalCode?: string }
212
247
  | { kind: 'duplicate_dispatch_refusal'; holderSessionId: string; holderAttemptId?: string }
213
248
  | { kind: 'session_rebound'; toSessionId: string; reason: SessionReboundReason }
214
249
  // ── worker turn lifecycle ──
@@ -232,7 +267,12 @@ export type TurnEvidenceBody =
232
267
  | { kind: 'process_exit'; exitCode: number | null; providerFailure?: ProviderFailure }
233
268
  | { kind: 'session_error'; reason: SessionErrorReason }
234
269
  // ── worker MCP (F2) ──
235
- | { kind: 'worker_report'; outcome: WorkerReportOutcome; summary: SummaryRef; hasHandoffNotes: boolean
270
+ /**
271
+ * `summary` is the handoff-topic pointer when the report's text was published;
272
+ * a report the owner daemon accepted itself carries its text in the evidence
273
+ * row's local envelope instead (never published), so the pointer is optional.
274
+ */
275
+ | { kind: 'worker_report'; outcome: WorkerReportOutcome; summary?: SummaryRef; hasHandoffNotes: boolean
236
276
  branchState?: WorkerBranchState; touchedFileCount?: number }
237
277
  | { kind: 'worker_progress'; note?: SummaryRef }
238
278
  // ── coordinator / operator ──
@@ -298,7 +338,7 @@ export const TURN_EVIDENCE_FIELD_SPECS: TurnEvidenceFieldSpecs = {
298
338
  },
299
339
  delivered: { messageId: id, outcome: en(DELIVERY_OUTCOMES), via: en(DELIVERY_VIAS) },
300
340
  delivery_refused: { messageId: id, reason: en(SEND_REFUSAL_REASONS) },
301
- dispatch_failed: { workerAbsent: bool, reason: en(DISPATCH_FAILURE_REASONS) },
341
+ dispatch_failed: { workerAbsent: bool, reason: en(DISPATCH_FAILURE_REASONS), refusalCode: idOpt },
302
342
  duplicate_dispatch_refusal: { holderSessionId: id, holderAttemptId: idOpt },
303
343
  session_rebound: { toSessionId: id, reason: en(SESSION_REBOUND_REASONS) },
304
344
  turn_started: { retro: bool },
@@ -320,7 +360,7 @@ export const TURN_EVIDENCE_FIELD_SPECS: TurnEvidenceFieldSpecs = {
320
360
  process_exit: { exitCode: { t: 'int', nullable: true }, providerFailure: enOpt(PROVIDER_FAILURES) },
321
361
  session_error: { reason: en(SESSION_ERROR_REASONS) },
322
362
  worker_report: {
323
- outcome: en(WORKER_REPORT_OUTCOMES), summary: { t: 'summary_ref' }, hasHandoffNotes: bool,
363
+ outcome: en(WORKER_REPORT_OUTCOMES), summary: { t: 'summary_ref', optional: true }, hasHandoffNotes: bool,
324
364
  branchState: enOpt(WORKER_BRANCH_STATES), touchedFileCount: intOpt,
325
365
  },
326
366
  worker_progress: { note: { t: 'summary_ref', optional: true } },