harness-dispatch 0.7.8 → 0.8.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +706 -1
  2. package/README.md +35 -5
  3. package/config.default.yaml +30 -2
  4. package/dist/auth.d.ts +29 -0
  5. package/dist/auth.d.ts.map +1 -1
  6. package/dist/auth.js +58 -5
  7. package/dist/auth.js.map +1 -1
  8. package/dist/bin.d.ts.map +1 -1
  9. package/dist/bin.js +449 -21
  10. package/dist/bin.js.map +1 -1
  11. package/dist/breaker-store.d.ts +25 -1
  12. package/dist/breaker-store.d.ts.map +1 -1
  13. package/dist/breaker-store.js +193 -25
  14. package/dist/breaker-store.js.map +1 -1
  15. package/dist/client-register.d.ts +147 -0
  16. package/dist/client-register.d.ts.map +1 -0
  17. package/dist/client-register.js +380 -0
  18. package/dist/client-register.js.map +1 -0
  19. package/dist/config/validation.d.ts +29 -0
  20. package/dist/config/validation.d.ts.map +1 -1
  21. package/dist/config/validation.js +88 -0
  22. package/dist/config/validation.js.map +1 -1
  23. package/dist/config.d.ts.map +1 -1
  24. package/dist/config.js +55 -2
  25. package/dist/config.js.map +1 -1
  26. package/dist/dispatch-log.d.ts +14 -0
  27. package/dist/dispatch-log.d.ts.map +1 -1
  28. package/dist/dispatch-log.js +3 -0
  29. package/dist/dispatch-log.js.map +1 -1
  30. package/dist/dispatchers/generic-cli.d.ts.map +1 -1
  31. package/dist/dispatchers/generic-cli.js +35 -0
  32. package/dist/dispatchers/generic-cli.js.map +1 -1
  33. package/dist/dispatchers/openai-compatible.d.ts +20 -4
  34. package/dist/dispatchers/openai-compatible.d.ts.map +1 -1
  35. package/dist/dispatchers/openai-compatible.js +145 -17
  36. package/dist/dispatchers/openai-compatible.js.map +1 -1
  37. package/dist/dispatchers/shared/rate-limit-headers.d.ts +4 -2
  38. package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -1
  39. package/dist/dispatchers/shared/rate-limit-headers.js +4 -2
  40. package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -1
  41. package/dist/http/answer-stream.d.ts +52 -0
  42. package/dist/http/answer-stream.d.ts.map +1 -0
  43. package/dist/http/answer-stream.js +47 -0
  44. package/dist/http/answer-stream.js.map +1 -0
  45. package/dist/http/parse.d.ts.map +1 -1
  46. package/dist/http/parse.js +41 -0
  47. package/dist/http/parse.js.map +1 -1
  48. package/dist/http/server.d.ts.map +1 -1
  49. package/dist/http/server.js +102 -9
  50. package/dist/http/server.js.map +1 -1
  51. package/dist/jobs/store.d.ts.map +1 -1
  52. package/dist/jobs/store.js +45 -4
  53. package/dist/jobs/store.js.map +1 -1
  54. package/dist/jobs.d.ts +24 -0
  55. package/dist/jobs.d.ts.map +1 -1
  56. package/dist/jobs.js +101 -1
  57. package/dist/jobs.js.map +1 -1
  58. package/dist/mcp/config-hot-reload.d.ts.map +1 -1
  59. package/dist/mcp/config-hot-reload.js +10 -0
  60. package/dist/mcp/config-hot-reload.js.map +1 -1
  61. package/dist/mcp/near-miss-guard.d.ts +45 -0
  62. package/dist/mcp/near-miss-guard.d.ts.map +1 -0
  63. package/dist/mcp/near-miss-guard.js +98 -0
  64. package/dist/mcp/near-miss-guard.js.map +1 -0
  65. package/dist/mcp/server.d.ts.map +1 -1
  66. package/dist/mcp/server.js +36 -0
  67. package/dist/mcp/server.js.map +1 -1
  68. package/dist/mcp/tool-schemas.d.ts.map +1 -1
  69. package/dist/mcp/tool-schemas.js +21 -4
  70. package/dist/mcp/tool-schemas.js.map +1 -1
  71. package/dist/mcp/tools.d.ts +32 -0
  72. package/dist/mcp/tools.d.ts.map +1 -1
  73. package/dist/mcp/tools.js +41 -0
  74. package/dist/mcp/tools.js.map +1 -1
  75. package/dist/mcp-clients.d.ts +63 -0
  76. package/dist/mcp-clients.d.ts.map +1 -0
  77. package/dist/mcp-clients.js +117 -0
  78. package/dist/mcp-clients.js.map +1 -0
  79. package/dist/near-miss.d.ts +63 -0
  80. package/dist/near-miss.d.ts.map +1 -0
  81. package/dist/near-miss.js +132 -0
  82. package/dist/near-miss.js.map +1 -0
  83. package/dist/quota.d.ts +31 -1
  84. package/dist/quota.d.ts.map +1 -1
  85. package/dist/quota.js +68 -6
  86. package/dist/quota.js.map +1 -1
  87. package/dist/route-policy.d.ts +10 -0
  88. package/dist/route-policy.d.ts.map +1 -1
  89. package/dist/route-policy.js +20 -2
  90. package/dist/route-policy.js.map +1 -1
  91. package/dist/router.d.ts +61 -2
  92. package/dist/router.d.ts.map +1 -1
  93. package/dist/router.js +165 -29
  94. package/dist/router.js.map +1 -1
  95. package/dist/safety.d.ts.map +1 -1
  96. package/dist/safety.js +31 -1
  97. package/dist/safety.js.map +1 -1
  98. package/dist/status.d.ts +30 -21
  99. package/dist/status.d.ts.map +1 -1
  100. package/dist/status.js +152 -8
  101. package/dist/status.js.map +1 -1
  102. package/dist/types.d.ts +43 -1
  103. package/dist/types.d.ts.map +1 -1
  104. package/dist/working-dir.d.ts.map +1 -1
  105. package/dist/working-dir.js +16 -0
  106. package/dist/working-dir.js.map +1 -1
  107. package/dist/workspace-resolve.d.ts.map +1 -1
  108. package/dist/workspace-resolve.js +119 -15
  109. package/dist/workspace-resolve.js.map +1 -1
  110. package/dist/workspaces.d.ts +83 -0
  111. package/dist/workspaces.d.ts.map +1 -1
  112. package/dist/workspaces.js +288 -15
  113. package/dist/workspaces.js.map +1 -1
  114. package/package.json +4 -3
package/README.md CHANGED
@@ -55,14 +55,24 @@ timeout — or to the server itself restarting mid-run.**
55
55
 
56
56
  Needs Node.js `>=22.22.2` (so current LTS works) and at least one harness or endpoint.
57
57
 
58
+ `git` is optional but recommended: dispatch works without it, but the
59
+ `workspace` tool shells out to git to diff and apply an isolated run's changes,
60
+ and the `git_worktree` isolation policy needs it. `doctor` reports whether it
61
+ found one.
62
+
58
63
  ```bash
59
64
  npm install -g harness-dispatch
60
65
  harness-dispatch configure --yes
61
66
  harness-dispatch doctor --live
62
67
  ```
63
68
 
64
- `configure --yes` detects installed harnesses and writes `config.yaml`; without
65
- `--yes` it previews and writes nothing. `doctor` then checks the whole chain:
69
+ `configure --yes` detects installed harnesses, writes `config.yaml`, and then
70
+ offers to register this server with each MCP client it finds (Claude Code,
71
+ Cursor) — showing you what it would write, and what is already there, before
72
+ changing anything. `--no-clients` skips the offer and prints a snippet to paste
73
+ instead; `harness-dispatch connect` does the same registration later on its own,
74
+ and `connect --remove` undoes it. Without
75
+ `--yes` configure previews and writes nothing. `doctor` then checks the whole chain:
66
76
  binary, config load, harness detection, auth and billing classification, route
67
77
  readiness. `--live` goes further and routes one tiny real prompt, so you see a
68
78
  completion before wiring anything into your agent. The live probe never touches paid or
@@ -343,6 +353,10 @@ set `billing_kind:` / `paid_usage_possible:` explicitly once you know it.
343
353
  harness-dispatch # stdio MCP
344
354
  harness-dispatch configure # detect harnesses and prepare config
345
355
  harness-dispatch configure --print # inspect generated config YAML
356
+ harness-dispatch connect # register with the MCP clients you have
357
+ harness-dispatch connect --clients cursor # no prompt; ids from the listing it prints
358
+ harness-dispatch connect --dev # point clients at THIS checkout's build
359
+ harness-dispatch connect --remove # take the entry back out
346
360
  harness-dispatch doctor # validate install, auth, config, and routes
347
361
  harness-dispatch doctor --live # run one eligible live routed probe
348
362
  harness-dispatch doctor --live --allow-paid
@@ -371,7 +385,7 @@ version bump.
371
385
  | `dispatch` | Always starts new routed coding work — one task to the best-fit harness, or a fanout to several for independent opinions. Every call runs as a background job from the first moment: a fast task returns its full result inline (`completed: true`), a slow one returns `completed: false` plus a `jobId` to check on. Nothing is ever lost to a timeout — including the MCP call's own. |
372
386
  | `job_status` | Checks work started by `dispatch`. Pass the `jobId` it returned to get a `partialOutput` tail while running and the full `result` once done; omit `jobId` to list recent background dispatches (compact, newest first). |
373
387
  | `cancel_job` | Stops work started by `dispatch` — a wrong turn, a wrong directory, a superseded run. A job still waiting for a slot stops outright; a running one tears down within about a second (poll `job_status` to see it land), killing the agent CLI and its children. Files it already changed are **not** reverted, and a cancelled run is not counted as a route failure. |
374
- | `retry_job` | Re-runs a finished job's task from its own record — same prompt (as the delegate saw it), files, working directory, hints and workspace policy. Pass `service` to send the retry to a different route, which is the usual reason to retry: the task was fine and the route was not. Returns a new jobId; the original is untouched. |
388
+ | `retry_job` | Re-runs a finished job's task from its own record — same prompt (as the delegate saw it), files, working directory, hints and workspace policy. Pass `service` to send the retry to a different route, which is the usual reason to retry: the task was fine and the route was not — the original's model is left behind when the new route does not declare it, reported as `droppedModel`. Returns a new jobId; the original is untouched. |
375
389
  | `workspace` | For a job that ran with `workspacePolicy: "copy"` or `"git_worktree"`, the agent's changes live in an isolated workspace and were **never** applied to your project. `action: "diff"` returns the real patch; `"apply"` applies it (refusing when your project has uncommitted changes, since the patch was built against a clean base — `force: true` overrides); `"discard"` deletes the workspace. The full patch is always written to the job directory, so `git apply` by hand is available either way. |
376
390
  | `usage` | Per-route call counts, quota, billing kind, and breaker state — check this before passing an unfamiliar `hints.model`/`service`/`models` value, since those are not validated. Pass `listModels: <route id>` to fetch that `openai_compatible` route's live `GET /models` catalog instead of (or alongside) the summary. |
377
391
 
@@ -383,7 +397,11 @@ carries a `warning` field saying so.
383
397
  immediately, then waits up to `graceSeconds` (default 25) for it to finish. Within the
384
398
  window you get the complete result inline, exactly as if the call had blocked. Past it
385
399
  you get the `jobId` — call `job_status` with that `jobId` to see a `partialOutput` tail
386
- while it runs and the full `result` once `completed`. Because the run never depends on
400
+ while it runs and the full `result` once `completed`. Expect the `jobId` path to be
401
+ ordinary rather than exceptional: real agent-CLI work regularly runs for minutes, so on
402
+ this maintainer's install a little over half of live dispatches finish past the default
403
+ window. Treat a `completed: false` as the normal shape of a substantial task, not as a
404
+ sign anything went wrong. Because the run never depends on
387
405
  the MCP call staying open, a client-side timeout costs you the inline reply, never the
388
406
  work. Background runs default to a generous 60-minute ceiling meant only to catch a
389
407
  genuinely hung process (stuck waiting on input, a stalled network call), not to cap
@@ -440,9 +458,21 @@ Status is exposed as resources:
440
458
 
441
459
  Endpoints:
442
460
 
461
+ - `GET /health` — liveness, and the **only** route served without a token, so a
462
+ deploy gate or container probe can ask without being handed a credential. It
463
+ answers `{"status","service","version"}` and nothing else: no routes, no
464
+ endpoints, no quota, no config.
443
465
  - `POST /mcp` for streamable HTTP MCP
466
+ - `POST /v1/chat/completions` with `stream: true` sends the answer as SSE
467
+ deltas. An endpoint route streams its text as it arrives; a CLI harness emits
468
+ protocol on stdout, so its answer is sent once, at completion — the deltas
469
+ never carry harness protocol either way. **Streaming creates no job record**,
470
+ so unlike the non-streaming call there is no `jobId` to poll and an
471
+ interrupted stream cannot be recovered. Use the non-streaming form for work
472
+ you would mind losing.
444
473
  - `GET /v1/status` — full route/quota/billing/breaker detail (same shape as
445
- `harness-dispatch://status.json`)
474
+ `harness-dispatch://status.json`). Authenticated, because that answer is not
475
+ for strangers.
446
476
  - `GET /v1/usage` — per-route call counts, quota, billing kind, and breaker state only
447
477
  - `GET /v1/models` — OpenAI-style model list; each entry's `id` is a route id you can
448
478
  pass as `model` in `/v1/chat/completions`
@@ -47,7 +47,23 @@ clis:
47
47
  # line; {{tokens}} are substituted at dispatch time (see the reference at
48
48
  # the bottom of this file).
49
49
  protocol:
50
- args: ["-p", "{{prompt}}", "--output-format", "json", "{{safety}}", "{{model}}"]
50
+ # The prompt goes on STDIN, not in argv.
51
+ #
52
+ # `claude -p` accepts either; argv was the original choice and it is the
53
+ # one that carries every command-line defect this project has fixed. On
54
+ # Windows a .CMD/.bat target is re-spawned through cmd.exe, which caps
55
+ # the whole command line at 8,191 characters — so a long prompt was
56
+ # refused before it reached the harness at all, and the machinery
57
+ # deciding where that boundary sits had to replicate cross-spawn's own
58
+ # escaping to know. generic-cli.ts skips that check entirely when
59
+ # `stdin: true` (the guard is `if (!protocol.stdin)`), because a prompt
60
+ # on stdin has no such limit. codex_cli has always worked this way.
61
+ #
62
+ # Verified live: a 21,670-character prompt — 2.65x the 8,191-character ceiling, and
63
+ # measured at 20,064 characters as argv, which the old form refused
64
+ # outright — dispatched and answered.
65
+ stdin: true
66
+ args: ["-p", "--output-format", "json", "{{safety}}", "{{model}}"]
51
67
  model: { flag: "--model" }
52
68
  safety:
53
69
  read_only: ["--allowedTools", "Read", "--permission-mode", "plan"]
@@ -206,7 +222,19 @@ clis:
206
222
  protocol:
207
223
  # -p is Cursor's boolean "print mode" flag; the prompt itself is the
208
224
  # trailing positional {{prompt}}.
209
- args: ["-p", "--trust", "{{safety}}", "{{working_dir}}", "--output-format", "json", "{{model}}", "{{prompt}}"]
225
+ # Prompt on STDIN, for the same reason as claude_code_cli above: a
226
+ # prompt in argv is measured against cmd.exe's 8,191-character ceiling
227
+ # and escaped to get there, and this route is a `cursor-agent.CMD`
228
+ # PowerShell wrapper — the one that failed at ~9k characters and drove
229
+ # two releases of escaping work. generic-cli.ts skips the check
230
+ # entirely when `stdin: true`.
231
+ #
232
+ # Verified live before changing: `cursor-agent -p --trust
233
+ # --output-format json` with the prompt piped in returns
234
+ # {"type":"result","subtype":"success","is_error":false,...,"result":"pong"}.
235
+ # --trust is required or it refuses with a workspace-trust prompt.
236
+ stdin: true
237
+ args: ["-p", "--trust", "{{safety}}", "{{working_dir}}", "--output-format", "json", "{{model}}"]
210
238
  # Only read_only gets extra flags; the other profiles run print mode as
211
239
  # before. --trust is unrelated to capability — it just suppresses the
212
240
  # workspace-trust prompt, which would hang a non-interactive run.
package/dist/auth.d.ts CHANGED
@@ -2,8 +2,37 @@ export declare function authDir(): string;
2
2
  export declare function tokenPath(): string;
3
3
  export declare function generateHttpToken(): string;
4
4
  export declare function readHttpToken(): Promise<string | null>;
5
+ /**
6
+ * The token as it is on disk RIGHT NOW, for a running server to consult.
7
+ *
8
+ * A server read the token once at startup and held it forever, so `auth
9
+ * rotate` was a lie in both directions: an acceptance pass measured the old
10
+ * token still returning 200 after rotation, and the newly issued one being
11
+ * rejected with 401. Invalidating the old token is the entire reason anyone
12
+ * rotates a credential, so telling the user it rotated while the leaked value
13
+ * kept working is the worst possible outcome.
14
+ *
15
+ * Synchronous because it is consulted on the authorization path of every
16
+ * request, which is not async. The file is a few dozen bytes on local disk and
17
+ * the read is guarded by an mtime check in the caller.
18
+ */
19
+ export declare function readHttpTokenSync(): string | null;
20
+ /** Modification time of the token file, or 0 when there isn't one. */
21
+ export declare function httpTokenMtimeMs(): number;
5
22
  export declare function ensureHttpToken(): Promise<string>;
6
23
  export declare function rotateHttpToken(): Promise<string>;
24
+ /**
25
+ * Whether a request carries the expected bearer token.
26
+ *
27
+ * A `token` of null means NO AUTH IS CONFIGURED, and every request is
28
+ * authorized — this function fails open, deliberately, and a caller that
29
+ * cannot guarantee a token must not rely on it to deny anything. The HTTP
30
+ * server can: it calls `ensureHttpToken()` at startup and falls back to the
31
+ * token it read from disk on refresh, so null never reaches here from there.
32
+ *
33
+ * Spelled out because the fail-open branch is one line and reads like a
34
+ * guard clause rather than the policy decision it is.
35
+ */
7
36
  export declare function isAuthorized(authorizationHeader: string | string[] | undefined, token: string | null): boolean;
8
37
  export declare function maskToken(token: string): string;
9
38
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AASA,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS5D;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAOvD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAKvD;AAmBD,wBAAgB,YAAY,CAC1B,mBAAmB,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAClD,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,OAAO,CAOT;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/C"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AASA,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS5D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CASjD;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,IAAI,MAAM,CAMzC;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAOvD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAKvD;AAyBD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,mBAAmB,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAClD,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,OAAO,CAOT;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/C"}
package/dist/auth.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { randomBytes, timingSafeEqual } from "node:crypto";
2
- import { promises as fs } from "node:fs";
2
+ import { promises as fs, readFileSync, statSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { stateRoot } from "./state-dir.js";
5
5
  const TOKEN_ENV = "HARNESS_DISPATCH_HTTP_TOKEN";
@@ -24,6 +24,41 @@ export async function readHttpToken() {
24
24
  return null;
25
25
  }
26
26
  }
27
+ /**
28
+ * The token as it is on disk RIGHT NOW, for a running server to consult.
29
+ *
30
+ * A server read the token once at startup and held it forever, so `auth
31
+ * rotate` was a lie in both directions: an acceptance pass measured the old
32
+ * token still returning 200 after rotation, and the newly issued one being
33
+ * rejected with 401. Invalidating the old token is the entire reason anyone
34
+ * rotates a credential, so telling the user it rotated while the leaked value
35
+ * kept working is the worst possible outcome.
36
+ *
37
+ * Synchronous because it is consulted on the authorization path of every
38
+ * request, which is not async. The file is a few dozen bytes on local disk and
39
+ * the read is guarded by an mtime check in the caller.
40
+ */
41
+ export function readHttpTokenSync() {
42
+ const fromEnv = process.env[TOKEN_ENV];
43
+ if (fromEnv)
44
+ return fromEnv;
45
+ try {
46
+ const token = readFileSync(tokenPath(), "utf-8").trim();
47
+ return token || null;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ /** Modification time of the token file, or 0 when there isn't one. */
54
+ export function httpTokenMtimeMs() {
55
+ try {
56
+ return statSync(tokenPath()).mtimeMs;
57
+ }
58
+ catch {
59
+ return 0;
60
+ }
61
+ }
27
62
  export async function ensureHttpToken() {
28
63
  const existing = await readHttpToken();
29
64
  if (existing)
@@ -42,19 +77,37 @@ export async function rotateHttpToken() {
42
77
  /**
43
78
  * Constant-time string compare. `value === expected` short-circuits on the
44
79
  * first mismatching byte — a textbook timing side channel for guessing a
45
- * bearer token one byte at a time. When lengths differ we still run
46
- * timingSafeEqual (against a same-length dummy) rather than returning early,
47
- * so a length mismatch alone doesn't leak timing info either.
80
+ * bearer token one byte at a time.
81
+ *
82
+ * On a length mismatch it still does comparison work rather than returning
83
+ * early. That work is now sized by `expected`, not by `value`: the previous
84
+ * version compared the caller-supplied buffer against ITSELF, so its cost
85
+ * scaled with the length an attacker chose, and the comment claiming "a length
86
+ * mismatch alone doesn't leak timing info either" asserted a property the code
87
+ * did not have. The remaining signal is the same for every wrong length, which
88
+ * is what that sentence was meant to say.
48
89
  */
49
90
  function safeEqual(value, expected) {
50
91
  const valueBuf = Buffer.from(value, "utf8");
51
92
  const expectedBuf = Buffer.from(expected, "utf8");
52
93
  if (valueBuf.length !== expectedBuf.length) {
53
- timingSafeEqual(valueBuf, valueBuf);
94
+ timingSafeEqual(expectedBuf, expectedBuf);
54
95
  return false;
55
96
  }
56
97
  return timingSafeEqual(valueBuf, expectedBuf);
57
98
  }
99
+ /**
100
+ * Whether a request carries the expected bearer token.
101
+ *
102
+ * A `token` of null means NO AUTH IS CONFIGURED, and every request is
103
+ * authorized — this function fails open, deliberately, and a caller that
104
+ * cannot guarantee a token must not rely on it to deny anything. The HTTP
105
+ * server can: it calls `ensureHttpToken()` at startup and falls back to the
106
+ * token it read from disk on refresh, so null never reaches here from there.
107
+ *
108
+ * Spelled out because the fail-open branch is one line and reads like a
109
+ * guard clause rather than the policy decision it is.
110
+ */
58
111
  export function isAuthorized(authorizationHeader, token) {
59
112
  if (token === null)
60
113
  return true;
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/bin.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;GAEG;AAohBH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiH1D"}
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;GAEG;AAg8BH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgK1D"}