instar 0.28.95 → 0.28.97

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.
Files changed (51) hide show
  1. package/dashboard/index.html +249 -2
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +90 -1
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/core/MachineHeartbeat.d.ts +94 -0
  6. package/dist/core/MachineHeartbeat.d.ts.map +1 -0
  7. package/dist/core/MachineHeartbeat.js +155 -0
  8. package/dist/core/MachineHeartbeat.js.map +1 -0
  9. package/dist/core/ProjectAutoAdvancePoller.d.ts +107 -0
  10. package/dist/core/ProjectAutoAdvancePoller.d.ts.map +1 -0
  11. package/dist/core/ProjectAutoAdvancePoller.js +176 -0
  12. package/dist/core/ProjectAutoAdvancePoller.js.map +1 -0
  13. package/dist/core/ProjectRoundCompleteMessage.d.ts +158 -0
  14. package/dist/core/ProjectRoundCompleteMessage.d.ts.map +1 -0
  15. package/dist/core/ProjectRoundCompleteMessage.js +253 -0
  16. package/dist/core/ProjectRoundCompleteMessage.js.map +1 -0
  17. package/dist/core/ProjectRoundExecution.d.ts +111 -0
  18. package/dist/core/ProjectRoundExecution.d.ts.map +1 -0
  19. package/dist/core/ProjectRoundExecution.js +359 -0
  20. package/dist/core/ProjectRoundExecution.js.map +1 -0
  21. package/dist/core/ProjectRoundLock.d.ts +67 -0
  22. package/dist/core/ProjectRoundLock.d.ts.map +1 -0
  23. package/dist/core/ProjectRoundLock.js +161 -0
  24. package/dist/core/ProjectRoundLock.js.map +1 -0
  25. package/dist/core/ProjectRoundRunner.d.ts +146 -0
  26. package/dist/core/ProjectRoundRunner.d.ts.map +1 -0
  27. package/dist/core/ProjectRoundRunner.js +371 -0
  28. package/dist/core/ProjectRoundRunner.js.map +1 -0
  29. package/dist/core/ProjectRoundWorktrees.d.ts +57 -0
  30. package/dist/core/ProjectRoundWorktrees.d.ts.map +1 -0
  31. package/dist/core/ProjectRoundWorktrees.js +111 -0
  32. package/dist/core/ProjectRoundWorktrees.js.map +1 -0
  33. package/dist/server/AgentServer.d.ts +12 -0
  34. package/dist/server/AgentServer.d.ts.map +1 -1
  35. package/dist/server/AgentServer.js +3 -0
  36. package/dist/server/AgentServer.js.map +1 -1
  37. package/dist/server/routes.d.ts +19 -0
  38. package/dist/server/routes.d.ts.map +1 -1
  39. package/dist/server/routes.js +782 -7
  40. package/dist/server/routes.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/data/builtin-manifest.json +48 -48
  43. package/upgrades/0.28.96.md +347 -0
  44. package/upgrades/0.28.97.md +146 -0
  45. package/upgrades/side-effects/project-scope-phase1-7-skill-surface.md +175 -0
  46. package/upgrades/side-effects/project-scope-phase1b-connect-the-dots.md +184 -0
  47. package/upgrades/side-effects/project-scope-phase1b-pr3.md +228 -0
  48. package/upgrades/side-effects/project-scope-phase1b-pr4.md +198 -0
  49. package/upgrades/side-effects/project-scope-phase1b-pr5.md +150 -0
  50. package/upgrades/side-effects/project-scope-phase1b-pr6.md +136 -0
  51. package/upgrades/side-effects/project-scope-phase1b-pr7.md +175 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * MachineHeartbeat — per-machine liveness signal for multi-machine
3
+ * project ownership.
4
+ *
5
+ * Spec: docs/specs/PROJECT-SCOPE-SPEC.md § P5 ("Machine ownership for
6
+ * multi-machine coherence").
7
+ *
8
+ * Each machine writes `.instar/machine-health/<machineId>.json` every
9
+ * heartbeat interval (default 30 minutes). The file is GIT-SYNCED, so
10
+ * other machines see fresh heartbeats from any peer that's online.
11
+ *
12
+ * Leader election (claim-ownership) treats a heartbeat older than
13
+ * `staleThresholdMs` (default 48 hours) as evidence that the recorded
14
+ * owner has gone offline and the claim should be allowed to proceed.
15
+ *
16
+ * What this class is NOT responsible for:
17
+ * - The git-sync that pushes the heartbeat file (handled by the
18
+ * existing GitSync layer).
19
+ * - The 60-second wait-for-convergence that the spec requires of
20
+ * a claimer. That's the caller's responsibility — the heartbeat
21
+ * just provides the staleness query.
22
+ *
23
+ * File schema:
24
+ * {
25
+ * "machineId": "<stable id>",
26
+ * "hostname": "<os.hostname()>",
27
+ * "lastHeartbeatAt": "<ISO timestamp>",
28
+ * "instarVersion": "<package.json version>"
29
+ * }
30
+ *
31
+ * Corruption tolerance: a malformed heartbeat file is treated as a
32
+ * missing heartbeat (stale by definition). The next write overwrites
33
+ * it with a valid record.
34
+ */
35
+ export declare const DEFAULT_HEARTBEAT_INTERVAL_MS: number;
36
+ export declare const DEFAULT_STALE_THRESHOLD_MS: number;
37
+ export interface HeartbeatRecord {
38
+ machineId: string;
39
+ hostname: string;
40
+ lastHeartbeatAt: string;
41
+ instarVersion?: string;
42
+ }
43
+ export interface MachineHeartbeatConfig {
44
+ /** Absolute path to the agent's `.instar/` directory. */
45
+ stateDir: string;
46
+ /** Stable machine id (usually the coordinator's machine id). */
47
+ machineId: string;
48
+ /** Defaults to 30 minutes; tests dial this down. */
49
+ heartbeatIntervalMs?: number;
50
+ /** Defaults to 48h. Tests dial this down. */
51
+ staleThresholdMs?: number;
52
+ /** Optional version string written into the heartbeat record. */
53
+ instarVersion?: string;
54
+ /** Optional clock override (tests). */
55
+ now?: () => Date;
56
+ }
57
+ export declare class MachineHeartbeat {
58
+ private stateDir;
59
+ private machineId;
60
+ private heartbeatIntervalMs;
61
+ private staleThresholdMs;
62
+ private instarVersion?;
63
+ private now;
64
+ private timer;
65
+ constructor(config: MachineHeartbeatConfig);
66
+ /** Identity of THIS machine (read-only for routes that need it). */
67
+ get id(): string;
68
+ /** Write a heartbeat immediately and start the periodic timer. */
69
+ start(): void;
70
+ /** Stop the periodic timer. */
71
+ stop(): void;
72
+ /** Write a single heartbeat. Public so server-startup paths can write
73
+ * one before the first interval fires. */
74
+ writeOnce(): HeartbeatRecord;
75
+ /** Read the heartbeat for any machine. Returns null on missing or malformed. */
76
+ read(machineId: string): HeartbeatRecord | null;
77
+ /**
78
+ * Is the heartbeat for `machineId` stale?
79
+ *
80
+ * Returns true when:
81
+ * - the heartbeat file is missing
82
+ * - the heartbeat file is malformed (defense-in-depth)
83
+ * - `lastHeartbeatAt` is older than `staleThresholdMs` ago
84
+ *
85
+ * Returns false when the heartbeat is within the threshold.
86
+ */
87
+ isStale(machineId: string): boolean;
88
+ /** List all known heartbeats (one per machine). */
89
+ listAll(): HeartbeatRecord[];
90
+ private ensureDir;
91
+ private dirPath;
92
+ private recordPath;
93
+ }
94
+ //# sourceMappingURL=MachineHeartbeat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MachineHeartbeat.d.ts","sourceRoot":"","sources":["../../src/core/MachineHeartbeat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAMH,eAAO,MAAM,6BAA6B,QAAiB,CAAC;AAC5D,eAAO,MAAM,0BAA0B,QAAsB,CAAC;AAE9D,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,KAAK,CAA+C;gBAEhD,MAAM,EAAE,sBAAsB;IAS1C,oEAAoE;IACpE,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,kEAAkE;IAClE,KAAK,IAAI,IAAI;IAQb,+BAA+B;IAC/B,IAAI,IAAI,IAAI;IAOZ;+CAC2C;IAC3C,SAAS,IAAI,eAAe;IAc5B,gFAAgF;IAChF,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAa/C;;;;;;;;;OASG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAQnC,mDAAmD;IACnD,OAAO,IAAI,eAAe,EAAE;IAW5B,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,UAAU;CAMnB"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * MachineHeartbeat — per-machine liveness signal for multi-machine
3
+ * project ownership.
4
+ *
5
+ * Spec: docs/specs/PROJECT-SCOPE-SPEC.md § P5 ("Machine ownership for
6
+ * multi-machine coherence").
7
+ *
8
+ * Each machine writes `.instar/machine-health/<machineId>.json` every
9
+ * heartbeat interval (default 30 minutes). The file is GIT-SYNCED, so
10
+ * other machines see fresh heartbeats from any peer that's online.
11
+ *
12
+ * Leader election (claim-ownership) treats a heartbeat older than
13
+ * `staleThresholdMs` (default 48 hours) as evidence that the recorded
14
+ * owner has gone offline and the claim should be allowed to proceed.
15
+ *
16
+ * What this class is NOT responsible for:
17
+ * - The git-sync that pushes the heartbeat file (handled by the
18
+ * existing GitSync layer).
19
+ * - The 60-second wait-for-convergence that the spec requires of
20
+ * a claimer. That's the caller's responsibility — the heartbeat
21
+ * just provides the staleness query.
22
+ *
23
+ * File schema:
24
+ * {
25
+ * "machineId": "<stable id>",
26
+ * "hostname": "<os.hostname()>",
27
+ * "lastHeartbeatAt": "<ISO timestamp>",
28
+ * "instarVersion": "<package.json version>"
29
+ * }
30
+ *
31
+ * Corruption tolerance: a malformed heartbeat file is treated as a
32
+ * missing heartbeat (stale by definition). The next write overwrites
33
+ * it with a valid record.
34
+ */
35
+ import fs from 'node:fs';
36
+ import os from 'node:os';
37
+ import path from 'node:path';
38
+ export const DEFAULT_HEARTBEAT_INTERVAL_MS = 30 * 60 * 1000; // 30 min
39
+ export const DEFAULT_STALE_THRESHOLD_MS = 48 * 60 * 60 * 1000; // 48h
40
+ export class MachineHeartbeat {
41
+ stateDir;
42
+ machineId;
43
+ heartbeatIntervalMs;
44
+ staleThresholdMs;
45
+ instarVersion;
46
+ now;
47
+ timer = null;
48
+ constructor(config) {
49
+ this.stateDir = config.stateDir;
50
+ this.machineId = config.machineId;
51
+ this.heartbeatIntervalMs = config.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
52
+ this.staleThresholdMs = config.staleThresholdMs ?? DEFAULT_STALE_THRESHOLD_MS;
53
+ this.instarVersion = config.instarVersion;
54
+ this.now = config.now ?? (() => new Date());
55
+ }
56
+ /** Identity of THIS machine (read-only for routes that need it). */
57
+ get id() {
58
+ return this.machineId;
59
+ }
60
+ /** Write a heartbeat immediately and start the periodic timer. */
61
+ start() {
62
+ this.writeOnce();
63
+ if (this.timer)
64
+ return;
65
+ this.timer = setInterval(() => this.writeOnce(), this.heartbeatIntervalMs);
66
+ // .unref() so heartbeat does not keep the process alive on shutdown.
67
+ if (typeof this.timer.unref === 'function')
68
+ this.timer.unref();
69
+ }
70
+ /** Stop the periodic timer. */
71
+ stop() {
72
+ if (this.timer) {
73
+ clearInterval(this.timer);
74
+ this.timer = null;
75
+ }
76
+ }
77
+ /** Write a single heartbeat. Public so server-startup paths can write
78
+ * one before the first interval fires. */
79
+ writeOnce() {
80
+ this.ensureDir();
81
+ const record = {
82
+ machineId: this.machineId,
83
+ hostname: os.hostname(),
84
+ lastHeartbeatAt: this.now().toISOString(),
85
+ instarVersion: this.instarVersion,
86
+ };
87
+ const tmp = this.recordPath(this.machineId) + '.tmp';
88
+ fs.writeFileSync(tmp, JSON.stringify(record, null, 2), { mode: 0o644 });
89
+ fs.renameSync(tmp, this.recordPath(this.machineId));
90
+ return record;
91
+ }
92
+ /** Read the heartbeat for any machine. Returns null on missing or malformed. */
93
+ read(machineId) {
94
+ try {
95
+ const raw = fs.readFileSync(this.recordPath(machineId), 'utf-8');
96
+ const obj = JSON.parse(raw);
97
+ if (typeof obj.machineId !== 'string')
98
+ return null;
99
+ if (typeof obj.hostname !== 'string')
100
+ return null;
101
+ if (typeof obj.lastHeartbeatAt !== 'string')
102
+ return null;
103
+ return obj;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ /**
110
+ * Is the heartbeat for `machineId` stale?
111
+ *
112
+ * Returns true when:
113
+ * - the heartbeat file is missing
114
+ * - the heartbeat file is malformed (defense-in-depth)
115
+ * - `lastHeartbeatAt` is older than `staleThresholdMs` ago
116
+ *
117
+ * Returns false when the heartbeat is within the threshold.
118
+ */
119
+ isStale(machineId) {
120
+ const r = this.read(machineId);
121
+ if (!r)
122
+ return true;
123
+ const t = Date.parse(r.lastHeartbeatAt);
124
+ if (!Number.isFinite(t))
125
+ return true;
126
+ return this.now().getTime() - t > this.staleThresholdMs;
127
+ }
128
+ /** List all known heartbeats (one per machine). */
129
+ listAll() {
130
+ this.ensureDir();
131
+ const entries = fs.readdirSync(this.dirPath()).filter((n) => n.endsWith('.json'));
132
+ const out = [];
133
+ for (const name of entries) {
134
+ const r = this.read(name.replace(/\.json$/, ''));
135
+ if (r)
136
+ out.push(r);
137
+ }
138
+ return out;
139
+ }
140
+ ensureDir() {
141
+ const p = this.dirPath();
142
+ if (!fs.existsSync(p))
143
+ fs.mkdirSync(p, { recursive: true });
144
+ }
145
+ dirPath() {
146
+ return path.join(this.stateDir, 'machine-health');
147
+ }
148
+ recordPath(machineId) {
149
+ // Sanitize: only [A-Za-z0-9_-] allowed in file names. Anything else
150
+ // gets URL-encoded so a stray slash or `..` can't escape.
151
+ const safe = machineId.replace(/[^A-Za-z0-9_-]/g, (c) => `%${c.charCodeAt(0).toString(16).padStart(2, '0')}`);
152
+ return path.join(this.dirPath(), `${safe}.json`);
153
+ }
154
+ }
155
+ //# sourceMappingURL=MachineHeartbeat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MachineHeartbeat.js","sourceRoot":"","sources":["../../src/core/MachineHeartbeat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * ProjectAutoAdvancePoller — periodic scan for project rounds whose
3
+ * `autoAdvanceAt` has elapsed and that pass the runner's preflight.
4
+ *
5
+ * Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.5 ("Auto-advance
6
+ * polling").
7
+ *
8
+ * Filter (server-side, NOT a full ledger scan):
9
+ * - `kind: 'project'`
10
+ * - `status: 'active'`
11
+ * - has at least one round with `autoAdvanceAt <= now`
12
+ * - `unacknowledgedAdvanceCount < 2` (cap brake)
13
+ * - `ownerMachineId` matches OR is empty AND no other machine has
14
+ * claimed (the heartbeat staleness check is the claim-flow's
15
+ * responsibility, not the poller's)
16
+ * - the project lock is not held
17
+ *
18
+ * On fire:
19
+ * 1. Run `ProjectRoundRunner.preflight(projectId, nextRoundIdx)`.
20
+ * 2. If preflight rejects, surface to telemetry + leave
21
+ * `autoAdvanceAt` in place so a future scan can retry — UNLESS
22
+ * the rejection is structural (`FIRST_LAUNCH_ACK_REQUIRED`,
23
+ * `UNACKED_ADVANCES_OVER_CAP`, `PROJECT_INACTIVE`,
24
+ * `PROJECT_HALTED`), in which case we clear `autoAdvanceAt`
25
+ * (no point retrying) and increment a structural-skip counter.
26
+ * 3. If preflight passes, increment `unacknowledgedAdvanceCount` and
27
+ * clear the current round's `autoAdvanceAt`. The autonomous
28
+ * run loop (next PR) is the one that actually starts the round
29
+ * work; for this PR, we record the bookkeeping move and rely on
30
+ * a future `run()` consumer.
31
+ *
32
+ * Why we don't run the actual round here: the autonomous-delegating
33
+ * run loop with worktrees, dynamic stop revalidation, and SIGTERM
34
+ * handling ships separately. Splitting the poller out lets the bookkeeping
35
+ * move ship today and exercises the filter logic end-to-end.
36
+ *
37
+ * Polling cadence is caller-driven (the existing scheduler `nextCheckAt`
38
+ * tick or a direct `tick()` call from a test).
39
+ */
40
+ import type { InitiativeTracker } from './InitiativeTracker.js';
41
+ import type { ProjectRoundRunner } from './ProjectRoundRunner.js';
42
+ export interface PollerTickResult {
43
+ scanned: number;
44
+ fired: string[];
45
+ rejected: Array<{
46
+ projectId: string;
47
+ code: string;
48
+ reason: string;
49
+ }>;
50
+ cleared: string[];
51
+ /** projectIds where the optional executor was launched (fire-and-forget). */
52
+ executed: string[];
53
+ /** Errors raised by the executor (settled async; one entry per failed launch). */
54
+ executorErrors: Array<{
55
+ projectId: string;
56
+ roundIndex: number;
57
+ error: string;
58
+ }>;
59
+ }
60
+ /**
61
+ * Optional fire-and-forget executor invoked after a successful preflight
62
+ * + bookkeeping move. When set, the poller launches the round run
63
+ * asynchronously. The executor is responsible for its own lock acquisition
64
+ * (`ProjectRoundLock`) so two ticks that both pass preflight cannot
65
+ * spawn two concurrent runs for the same project.
66
+ *
67
+ * The promise is NOT awaited inside `tick()` — the poller's job is
68
+ * scanning, not blocking on multi-minute runs. Errors from the executor
69
+ * are caught and surfaced via `result.executorErrors` so the next tick
70
+ * has a record of what happened.
71
+ */
72
+ export type ProjectRoundExecutor = (input: {
73
+ projectId: string;
74
+ roundIndex: number;
75
+ }) => Promise<void>;
76
+ export interface ProjectAutoAdvancePollerConfig {
77
+ tracker: InitiativeTracker;
78
+ runner: ProjectRoundRunner;
79
+ /** Stable machine id (used for ownerMachineId comparison). */
80
+ machineId: string;
81
+ now?: () => Date;
82
+ /**
83
+ * Optional async executor that actually launches the round work. When
84
+ * omitted, the poller does bookkeeping only (PR-5-era behavior). When
85
+ * present, fire-and-forget invocation after `bookKeepFire`.
86
+ */
87
+ executor?: ProjectRoundExecutor;
88
+ }
89
+ export declare class ProjectAutoAdvancePoller {
90
+ private tracker;
91
+ private runner;
92
+ private machineId;
93
+ private now;
94
+ private executor?;
95
+ /** Tracks in-flight executor invocations so a slow run doesn't get relaunched on the next tick. */
96
+ private inFlight;
97
+ constructor(config: ProjectAutoAdvancePollerConfig);
98
+ /** Test/diagnostic helper: how many executor launches are still settling. */
99
+ inFlightCount(): number;
100
+ /** Run one pass over all eligible projects. */
101
+ tick(): Promise<PollerTickResult>;
102
+ /** Fire-and-forget executor launch. Errors are captured into `result.executorErrors`. */
103
+ private launchExecutor;
104
+ private clearAutoAdvance;
105
+ private bookKeepFire;
106
+ }
107
+ //# sourceMappingURL=ProjectAutoAdvancePoller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectAutoAdvancePoller.d.ts","sourceRoot":"","sources":["../../src/core/ProjectAutoAdvancePoller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAc,MAAM,wBAAwB,CAAC;AAC5E,OAAO,KAAK,EAAE,kBAAkB,EAAmB,MAAM,yBAAyB,CAAC;AAEnF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,kFAAkF;IAClF,cAAc,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpB,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAcD,qBAAa,wBAAwB;IACnC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,CAAuB;IACxC,mGAAmG;IACnG,OAAO,CAAC,QAAQ,CAA0B;gBAE9B,MAAM,EAAE,8BAA8B;IAQlD,6EAA6E;IAC7E,aAAa,IAAI,MAAM;IAIvB,+CAA+C;IACzC,IAAI,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAsEvC,yFAAyF;IACzF,OAAO,CAAC,cAAc;YAgBR,gBAAgB;YAWhB,YAAY;CAe3B"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * ProjectAutoAdvancePoller — periodic scan for project rounds whose
3
+ * `autoAdvanceAt` has elapsed and that pass the runner's preflight.
4
+ *
5
+ * Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.5 ("Auto-advance
6
+ * polling").
7
+ *
8
+ * Filter (server-side, NOT a full ledger scan):
9
+ * - `kind: 'project'`
10
+ * - `status: 'active'`
11
+ * - has at least one round with `autoAdvanceAt <= now`
12
+ * - `unacknowledgedAdvanceCount < 2` (cap brake)
13
+ * - `ownerMachineId` matches OR is empty AND no other machine has
14
+ * claimed (the heartbeat staleness check is the claim-flow's
15
+ * responsibility, not the poller's)
16
+ * - the project lock is not held
17
+ *
18
+ * On fire:
19
+ * 1. Run `ProjectRoundRunner.preflight(projectId, nextRoundIdx)`.
20
+ * 2. If preflight rejects, surface to telemetry + leave
21
+ * `autoAdvanceAt` in place so a future scan can retry — UNLESS
22
+ * the rejection is structural (`FIRST_LAUNCH_ACK_REQUIRED`,
23
+ * `UNACKED_ADVANCES_OVER_CAP`, `PROJECT_INACTIVE`,
24
+ * `PROJECT_HALTED`), in which case we clear `autoAdvanceAt`
25
+ * (no point retrying) and increment a structural-skip counter.
26
+ * 3. If preflight passes, increment `unacknowledgedAdvanceCount` and
27
+ * clear the current round's `autoAdvanceAt`. The autonomous
28
+ * run loop (next PR) is the one that actually starts the round
29
+ * work; for this PR, we record the bookkeeping move and rely on
30
+ * a future `run()` consumer.
31
+ *
32
+ * Why we don't run the actual round here: the autonomous-delegating
33
+ * run loop with worktrees, dynamic stop revalidation, and SIGTERM
34
+ * handling ships separately. Splitting the poller out lets the bookkeeping
35
+ * move ship today and exercises the filter logic end-to-end.
36
+ *
37
+ * Polling cadence is caller-driven (the existing scheduler `nextCheckAt`
38
+ * tick or a direct `tick()` call from a test).
39
+ */
40
+ /** Reject codes that mean "stop retrying" — clear autoAdvanceAt. */
41
+ const STRUCTURAL_REJECTS = new Set([
42
+ 'FIRST_LAUNCH_ACK_REQUIRED',
43
+ 'UNACKED_ADVANCES_OVER_CAP',
44
+ 'ROUND_ACK_GAP_TOO_LARGE',
45
+ 'PROJECT_INACTIVE',
46
+ 'PROJECT_HALTED',
47
+ 'PROJECT_NOT_PROJECT_KIND',
48
+ 'PROJECT_NOT_FOUND',
49
+ 'TARGET_REPO_PATH_INVALID',
50
+ ]);
51
+ export class ProjectAutoAdvancePoller {
52
+ tracker;
53
+ runner;
54
+ machineId;
55
+ now;
56
+ executor;
57
+ /** Tracks in-flight executor invocations so a slow run doesn't get relaunched on the next tick. */
58
+ inFlight = new Set();
59
+ constructor(config) {
60
+ this.tracker = config.tracker;
61
+ this.runner = config.runner;
62
+ this.machineId = config.machineId;
63
+ this.now = config.now ?? (() => new Date());
64
+ this.executor = config.executor;
65
+ }
66
+ /** Test/diagnostic helper: how many executor launches are still settling. */
67
+ inFlightCount() {
68
+ return this.inFlight.size;
69
+ }
70
+ /** Run one pass over all eligible projects. */
71
+ async tick() {
72
+ const result = {
73
+ scanned: 0,
74
+ fired: [],
75
+ rejected: [],
76
+ cleared: [],
77
+ executed: [],
78
+ executorErrors: [],
79
+ };
80
+ // Filter server-side: kind=project + status=active. (The tracker's
81
+ // `list` already supports the kind filter.)
82
+ const projects = this.tracker.list({ kind: 'project', status: 'active' });
83
+ const nowMs = this.now().getTime();
84
+ for (const project of projects) {
85
+ result.scanned++;
86
+ // Step 1: ownership filter — skip projects this machine doesn't own.
87
+ // Empty ownerMachineId is treated as "any machine can claim by
88
+ // running" — but for auto-advance we require explicit ownership
89
+ // so two machines don't race. Claim-ownership endpoint is the
90
+ // explicit takeover path.
91
+ if (project.ownerMachineId && project.ownerMachineId !== this.machineId)
92
+ continue;
93
+ // Step 2: cap brake — over-cap projects never auto-fire.
94
+ const unacked = project.unacknowledgedAdvanceCount ?? 0;
95
+ if (unacked >= 2)
96
+ continue;
97
+ // Step 3: find the first round with autoAdvanceAt elapsed.
98
+ const rounds = project.rounds ?? [];
99
+ const roundIdx = rounds.findIndex((r) => {
100
+ const t = r.autoAdvanceAt ? Date.parse(r.autoAdvanceAt) : 0;
101
+ return Number.isFinite(t) && t > 0 && t <= nowMs && (r.status ?? 'pending') === 'pending';
102
+ });
103
+ if (roundIdx === -1)
104
+ continue;
105
+ // Step 4: preflight via the runner.
106
+ const pre = this.runner.preflight(project.id, roundIdx);
107
+ if (!pre.ok) {
108
+ result.rejected.push({ projectId: project.id, code: pre.code, reason: pre.reason });
109
+ if (STRUCTURAL_REJECTS.has(pre.code)) {
110
+ // Clear the timestamp; no point in retrying until structural
111
+ // state changes (which will happen via a different code path —
112
+ // user ack, halt-then-resume, etc.).
113
+ await this.clearAutoAdvance(project, roundIdx);
114
+ result.cleared.push(project.id);
115
+ }
116
+ continue;
117
+ }
118
+ // Step 5: bookkeeping move — increment unacked counter, clear
119
+ // current round's autoAdvanceAt so we don't re-fire on the next
120
+ // tick.
121
+ await this.bookKeepFire(project, roundIdx);
122
+ result.fired.push(project.id);
123
+ // Step 6: fire-and-forget executor launch (if configured).
124
+ // The executor (ProjectRoundExecution.runRound) acquires its own
125
+ // lock, so a duplicate-launch on a stuck tick would resolve to
126
+ // LOCK_HELD inside the runner. We additionally guard with
127
+ // `inFlight` so we don't even attempt while one is settling.
128
+ if (this.executor && !this.inFlight.has(project.id)) {
129
+ result.executed.push(project.id);
130
+ this.launchExecutor(project.id, roundIdx, result);
131
+ }
132
+ }
133
+ return result;
134
+ }
135
+ /** Fire-and-forget executor launch. Errors are captured into `result.executorErrors`. */
136
+ launchExecutor(projectId, roundIndex, result) {
137
+ if (!this.executor)
138
+ return;
139
+ this.inFlight.add(projectId);
140
+ void this.executor({ projectId, roundIndex })
141
+ .catch((err) => {
142
+ result.executorErrors.push({
143
+ projectId,
144
+ roundIndex,
145
+ error: err instanceof Error ? err.message : String(err),
146
+ });
147
+ })
148
+ .finally(() => {
149
+ this.inFlight.delete(projectId);
150
+ });
151
+ }
152
+ async clearAutoAdvance(project, roundIdx) {
153
+ const rounds = (project.rounds ?? []).map((r, i) => i === roundIdx ? { ...r, autoAdvanceAt: undefined } : r);
154
+ try {
155
+ await this.tracker.update(project.id, { rounds, ifMatch: project.version });
156
+ }
157
+ catch {
158
+ // OCC race — another writer beat us. Next tick will see the new state.
159
+ }
160
+ }
161
+ async bookKeepFire(project, roundIdx) {
162
+ const rounds = (project.rounds ?? []).map((r, i) => i === roundIdx ? { ...r, autoAdvanceAt: undefined } : r);
163
+ const nextUnacked = (project.unacknowledgedAdvanceCount ?? 0) + 1;
164
+ try {
165
+ await this.tracker.update(project.id, {
166
+ rounds,
167
+ unacknowledgedAdvanceCount: nextUnacked,
168
+ ifMatch: project.version,
169
+ });
170
+ }
171
+ catch {
172
+ // OCC race — next tick will retry.
173
+ }
174
+ }
175
+ }
176
+ //# sourceMappingURL=ProjectAutoAdvancePoller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectAutoAdvancePoller.js","sourceRoot":"","sources":["../../src/core/ProjectAutoAdvancePoller.ts"],"names":[],"mappings":"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"}