tickmarkr 1.69.0 → 1.71.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.
@@ -15,13 +15,18 @@ export interface ResumeState {
15
15
  tried: string[];
16
16
  lastAssignment?: Assignment;
17
17
  }
18
+ export declare const CHANNEL_EXCLUSION_KINDS: readonly ["dead-channel"];
19
+ export type ChannelExclusionKind = (typeof CHANNEL_EXCLUSION_KINDS)[number];
18
20
  export declare const ATTEMPT_CAP_RELEASE: "attempt-cap";
19
- export declare const PARK_KINDS: readonly ["human-gate", "ladder-exhausted", "attempt-cap", "gate-fail", "quota", "reroute-exhausted", "setup", "stall", "merge-conflict", "tip-moved"];
21
+ export declare const PARK_KINDS: readonly ["human-gate", "ladder-exhausted", "attempt-cap", "gate-fail", "quota", "reroute-exhausted", "setup", "stall", "merge-conflict", "tip-moved", "infra", "dispatch"];
20
22
  export type ParkKind = (typeof PARK_KINDS)[number];
21
23
  export declare const RETRY_MODES: readonly ["resume", "fresh"];
22
24
  export type RetryMode = (typeof RETRY_MODES)[number];
23
25
  export declare const WORKER_RESULT_CAUSES: readonly ["provider-death", "stall-timeout", "malformed-trailer", "clean-exit-no-trailer"];
24
26
  export type WorkerResultCause = (typeof WORKER_RESULT_CAUSES)[number];
27
+ export declare function isQualityFailureParkKind(kind: ParkKind): boolean;
28
+ export declare function recordedTaskFailureKind(events: JournalEvent[], taskId: string): ParkKind | undefined;
29
+ export declare function runHasEnded(events: JournalEvent[]): boolean;
25
30
  /** OBS-53: classify worker-result failures so retries and routing see the true signal, not one lumped bucket. */
26
31
  export declare function classifyWorkerResultCause(opts: {
27
32
  output: string;
@@ -58,6 +63,8 @@ export declare const TelemetryRowSchema: z.ZodObject<{
58
63
  stall: "stall";
59
64
  "merge-conflict": "merge-conflict";
60
65
  "tip-moved": "tip-moved";
66
+ infra: "infra";
67
+ dispatch: "dispatch";
61
68
  }>>;
62
69
  tokens: z.ZodCatch<z.ZodOptional<z.ZodObject<{
63
70
  input: z.ZodNumber;
@@ -139,6 +146,7 @@ export declare class Journal {
139
146
  read(): JournalEvent[];
140
147
  replayStatuses(): Map<string, TaskStatus>;
141
148
  replayResumeState(): Map<string, ResumeState>;
149
+ replayExcludedChannels(): Set<string>;
142
150
  telemetry(row: TelemetryRow): void;
143
151
  readTelemetry(): TelemetryRow[];
144
152
  }
@@ -4,7 +4,7 @@ import { z } from "zod";
4
4
  import { channelKey, TokenUsageSchema } from "../adapters/types.js";
5
5
  import { stateDirName, tickmarkrDir } from "../graph/graph.js";
6
6
  import { TIERS } from "../graph/schema.js";
7
- import { buildProfile } from "../route/profile.js";
7
+ import { buildProfile, classify } from "../route/profile.js";
8
8
  import { redactSecrets } from "./redact.js";
9
9
  export function formatJournalNarration({ event, taskId, data }) {
10
10
  const assignment = data.assignment;
@@ -26,6 +26,9 @@ export function formatJournalNarration({ event, taskId, data }) {
26
26
  : String(direct);
27
27
  return [event, taskId, detail?.replace(/\s+/g, " ").slice(0, 120)].filter(Boolean).join(" — ");
28
28
  }
29
+ // v1.71 OBS-119: run-wide channel exclusions derived from journal events — companion to
30
+ // replayResumeState(), consumed by the daemon on resume to re-seed demotedChannels.
31
+ export const CHANNEL_EXCLUSION_KINDS = ["dead-channel"];
29
32
  // v1.24 OBS-18: explicit data on task-approved when the operator releases an attempt-cap park.
30
33
  // Approve stamps this; replayResumeState zeros the attempt counter (fresh budget) while keeping
31
34
  // tried (consult bans / burned channels). Absent on pre-v1.24 events ⇒ inert (corpus outcome-identical).
@@ -40,9 +43,43 @@ const DispatchAssignmentSchema = z.object({
40
43
  tier: z.enum(TIERS),
41
44
  });
42
45
  export const PARK_KINDS = ["human-gate", "ladder-exhausted", "attempt-cap", "gate-fail", "quota",
43
- "reroute-exhausted", "setup", "stall", "merge-conflict", "tip-moved"];
46
+ "reroute-exhausted", "setup", "stall", "merge-conflict", "tip-moved", "infra", "dispatch"];
44
47
  export const RETRY_MODES = ["resume", "fresh"];
45
48
  export const WORKER_RESULT_CAUSES = ["provider-death", "stall-timeout", "malformed-trailer", "clean-exit-no-trailer"];
49
+ // Status consumes the routing profile's existing quality split directly: verified park kinds classify
50
+ // to 0, while availability/recovery noise classifies to null. Keep the synthetic row here at the
51
+ // run↔route seam so presentation code never grows a second list of "bad" park kinds.
52
+ export function isQualityFailureParkKind(kind) {
53
+ return classify({
54
+ shape: "", adapter: "-", model: "-", channel: "-", attempts: 0,
55
+ outcome: "human", durationMs: 0, parkKind: kind,
56
+ }) === 0;
57
+ }
58
+ // The newest terminal event owns the cause. Unknown/malformed kinds fail toward undefined so a
59
+ // task-failed row keeps the legacy red treatment instead of being mistaken for recoverable noise.
60
+ export function recordedTaskFailureKind(events, taskId) {
61
+ for (let i = events.length - 1; i >= 0; i--) {
62
+ const e = events[i];
63
+ if (e.taskId !== taskId || (e.event !== "task-human" && e.event !== "task-failed"))
64
+ continue;
65
+ return typeof e.data.kind === "string" && PARK_KINDS.includes(e.data.kind)
66
+ ? e.data.kind
67
+ : undefined;
68
+ }
69
+ return undefined;
70
+ }
71
+ // Runs can end and later resume in the same journal. The newest lifecycle marker decides whether
72
+ // an unresolved task is still recoverable by this live daemon or belongs to an ended run.
73
+ export function runHasEnded(events) {
74
+ for (let i = events.length - 1; i >= 0; i--) {
75
+ const event = events[i].event;
76
+ if (event === "run-end" || event === "superseded")
77
+ return true;
78
+ if (event === "run-start" || event === "run-resume")
79
+ return false;
80
+ }
81
+ return false;
82
+ }
46
83
  // OBS-53: provider-outage signatures in dead worker output ("Unable to reach the model provider" and kin).
47
84
  const PROVIDER_OUTAGE_RE = /Unable to reach the model provider|cannot reach the model provider|model provider.*(?:unavailable|unreachable)/i;
48
85
  /** OBS-53: classify worker-result failures so retries and routing see the true signal, not one lumped bucket. */
@@ -424,6 +461,20 @@ export class Journal {
424
461
  }
425
462
  return m;
426
463
  }
464
+ // v1.71 OBS-119: run-wide exclusion fold — same replay discipline as replayResumeState().
465
+ // channel-exclusion is the typed event; dead-channel-failover.from is the pre-v1.71 compat seam.
466
+ replayExcludedChannels() {
467
+ const excluded = new Set();
468
+ for (const e of this.read()) {
469
+ if (e.event === "channel-exclusion" && typeof e.data.channel === "string") {
470
+ excluded.add(e.data.channel);
471
+ }
472
+ else if (e.event === "dead-channel-failover" && typeof e.data.from === "string") {
473
+ excluded.add(e.data.from);
474
+ }
475
+ }
476
+ return excluded;
477
+ }
427
478
  telemetry(row) {
428
479
  // T3 secret redaction: same persistence seam as append — credential-free rows are byte-identical.
429
480
  appendFileSync(join(this.dir, "telemetry.jsonl"), redactSecrets(JSON.stringify(row)) + "\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "1.69.0",
3
+ "version": "1.71.0",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",