@cat-factory/executor-harness 1.39.3 → 1.41.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/dist/server.js CHANGED
@@ -6,6 +6,7 @@ import { handleInline } from './inline.js';
6
6
  import { redactSecrets } from './git.js';
7
7
  import { JobRegistry, loadRunnerLimits } from './runner.js';
8
8
  import { log } from './logger.js';
9
+ import { HARNESS_VERSION } from './version.js';
9
10
  // The container's HTTP entry point. The Worker addresses one instance per run and
10
11
  // POSTs a job to /jobs (the body's `kind` selects which agent runs); the harness
11
12
  // starts that job in the background (bounded by an inactivity + max-duration
@@ -90,7 +91,14 @@ function send(res, status, body) {
90
91
  const server = createServer((req, res) => {
91
92
  void (async () => {
92
93
  if (req.method === 'GET' && req.url === '/health') {
93
- return send(res, 200, { status: 'ok' });
94
+ // Report the harness version so a backend can detect a stale/mismatched executor and
95
+ // fail loudly early (see version.ts). Unauthenticated like the rest of /health — the
96
+ // version is not a secret. An old image predating this field simply omits it, which the
97
+ // backend treats as a stale signal.
98
+ return send(res, 200, {
99
+ status: 'ok',
100
+ ...(HARNESS_VERSION ? { version: HARNESS_VERSION } : {}),
101
+ });
94
102
  }
95
103
  // All non-health endpoints are gated by the optional shared secret.
96
104
  if (!authorized(req)) {
@@ -0,0 +1,44 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ // The harness's OWN version, resolved once at module load and reported on `/health`. A
5
+ // backend reads it back over the health handshake to detect a STALE or MISMATCHED executor
6
+ // (an old image left behind by a mutable tag, an outdated native install) and fail loudly
7
+ // and early — instead of the cryptic downstream symptom a version skew otherwise produces
8
+ // (e.g. a since-removed git flag reappearing and breaking every authenticated clone/push).
9
+ //
10
+ // Resolution order, most authoritative first:
11
+ // 1. `HARNESS_VERSION` env — the Docker image can bake it; an operator can override.
12
+ // 2. the version file the image writes next to `dist/` (the image deliberately ships NO
13
+ // package.json, so it captures the version into `harness-version.txt` at build time).
14
+ // 3. `package.json` — the native/npm install and a source checkout both keep it beside
15
+ // `dist/`, so `dist/version.js` finds it one level up.
16
+ // Undefined only for an oddly-assembled runtime carrying none of the three; the backend then
17
+ // treats "no reported version" as a strong stale signal in its own right.
18
+ function readVersionFile(dir, rel) {
19
+ try {
20
+ const raw = readFileSync(join(dir, rel), 'utf8');
21
+ const value = rel.endsWith('.json') ? JSON.parse(raw).version : raw;
22
+ const trimmed = value?.trim();
23
+ return trimmed || undefined;
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ function resolveHarnessVersion() {
30
+ const fromEnv = process.env.HARNESS_VERSION?.trim();
31
+ if (fromEnv)
32
+ return fromEnv;
33
+ let dir;
34
+ try {
35
+ // Compiled to `dist/version.js`; the baked file / package.json sit one level up from dist.
36
+ dir = dirname(fileURLToPath(import.meta.url));
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ return readVersionFile(dir, '../harness-version.txt') ?? readVersionFile(dir, '../package.json');
42
+ }
43
+ /** The running harness version, or undefined when it cannot be determined. */
44
+ export const HARNESS_VERSION = resolveHarnessVersion();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/executor-harness",
3
- "version": "1.39.3",
3
+ "version": "1.41.0",
4
4
  "description": "Container payload: a thin TypeScript wrapper that runs the Pi coding agent against a cloned repo and opens a PR. Runs in the Cloudflare Container (and, in local native mode, as a host process); carries no secrets.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,8 +26,8 @@
26
26
  "hono": "^4.12.27",
27
27
  "typescript": "^6.0.3",
28
28
  "vitest": "^4.1.9",
29
- "@cat-factory/server": "0.106.0",
30
- "@cat-factory/spend": "0.12.0"
29
+ "@cat-factory/server": "0.106.1",
30
+ "@cat-factory/spend": "0.12.1"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsc -p tsconfig.json",
package/src/server.ts CHANGED
@@ -6,6 +6,7 @@ import { handleInline } from './inline.js'
6
6
  import { redactSecrets } from './git.js'
7
7
  import { JobRegistry, loadRunnerLimits, type JobResultBase, type RunOptions } from './runner.js'
8
8
  import { log } from './logger.js'
9
+ import { HARNESS_VERSION } from './version.js'
9
10
 
10
11
  // The container's HTTP entry point. The Worker addresses one instance per run and
11
12
  // POSTs a job to /jobs (the body's `kind` selects which agent runs); the harness
@@ -109,7 +110,14 @@ function send(res: ServerResponse, status: number, body: unknown): void {
109
110
  const server = createServer((req, res) => {
110
111
  void (async () => {
111
112
  if (req.method === 'GET' && req.url === '/health') {
112
- return send(res, 200, { status: 'ok' })
113
+ // Report the harness version so a backend can detect a stale/mismatched executor and
114
+ // fail loudly early (see version.ts). Unauthenticated like the rest of /health — the
115
+ // version is not a secret. An old image predating this field simply omits it, which the
116
+ // backend treats as a stale signal.
117
+ return send(res, 200, {
118
+ status: 'ok',
119
+ ...(HARNESS_VERSION ? { version: HARNESS_VERSION } : {}),
120
+ })
113
121
  }
114
122
  // All non-health endpoints are gated by the optional shared secret.
115
123
  if (!authorized(req)) {
package/src/version.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ // The harness's OWN version, resolved once at module load and reported on `/health`. A
6
+ // backend reads it back over the health handshake to detect a STALE or MISMATCHED executor
7
+ // (an old image left behind by a mutable tag, an outdated native install) and fail loudly
8
+ // and early — instead of the cryptic downstream symptom a version skew otherwise produces
9
+ // (e.g. a since-removed git flag reappearing and breaking every authenticated clone/push).
10
+ //
11
+ // Resolution order, most authoritative first:
12
+ // 1. `HARNESS_VERSION` env — the Docker image can bake it; an operator can override.
13
+ // 2. the version file the image writes next to `dist/` (the image deliberately ships NO
14
+ // package.json, so it captures the version into `harness-version.txt` at build time).
15
+ // 3. `package.json` — the native/npm install and a source checkout both keep it beside
16
+ // `dist/`, so `dist/version.js` finds it one level up.
17
+ // Undefined only for an oddly-assembled runtime carrying none of the three; the backend then
18
+ // treats "no reported version" as a strong stale signal in its own right.
19
+
20
+ function readVersionFile(dir: string, rel: string): string | undefined {
21
+ try {
22
+ const raw = readFileSync(join(dir, rel), 'utf8')
23
+ const value = rel.endsWith('.json') ? (JSON.parse(raw) as { version?: string }).version : raw
24
+ const trimmed = value?.trim()
25
+ return trimmed || undefined
26
+ } catch {
27
+ return undefined
28
+ }
29
+ }
30
+
31
+ function resolveHarnessVersion(): string | undefined {
32
+ const fromEnv = process.env.HARNESS_VERSION?.trim()
33
+ if (fromEnv) return fromEnv
34
+ let dir: string
35
+ try {
36
+ // Compiled to `dist/version.js`; the baked file / package.json sit one level up from dist.
37
+ dir = dirname(fileURLToPath(import.meta.url))
38
+ } catch {
39
+ return undefined
40
+ }
41
+ return readVersionFile(dir, '../harness-version.txt') ?? readVersionFile(dir, '../package.json')
42
+ }
43
+
44
+ /** The running harness version, or undefined when it cannot be determined. */
45
+ export const HARNESS_VERSION: string | undefined = resolveHarnessVersion()