@code-partner/codepipe-hub 0.14.1-dev.422.gf90f06c4 → 0.14.1-dev.423.ga49d1f61

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.
@@ -457,7 +457,7 @@ var PLATFORM_VERSION;
457
457
  var init_version_generated = __esm({
458
458
  "../packages/shared/dist/version.generated.js"() {
459
459
  "use strict";
460
- PLATFORM_VERSION = "0.14.1-dev.422.gf90f06c4";
460
+ PLATFORM_VERSION = "0.14.1-dev.423.ga49d1f61";
461
461
  }
462
462
  });
463
463
 
@@ -467,7 +467,7 @@ var init_version = __esm({
467
467
  "../packages/shared/dist/version.js"() {
468
468
  "use strict";
469
469
  init_version_generated();
470
- PROTOCOL_VERSION = 4;
470
+ PROTOCOL_VERSION = 5;
471
471
  }
472
472
  });
473
473
 
@@ -4983,6 +4983,8 @@ function createCommandOperations(deps) {
4983
4983
  throw errors.unprocessable({ details: { reason: outcome.reason } });
4984
4984
  case "not_found":
4985
4985
  throw errors.notFound();
4986
+ case "rate_limited":
4987
+ throw errors.rateLimited();
4986
4988
  }
4987
4989
  }
4988
4990
  function accountActorOf(actor) {
@@ -5572,6 +5574,14 @@ function createCommandOperations(deps) {
5572
5574
  return { value: run.result, replayed: run.replayed };
5573
5575
  });
5574
5576
  },
5577
+ async issueWorkerRegistrationToken(actor) {
5578
+ const subjectId = actor.principal.subjectId;
5579
+ return await audited(actor, { operationId: "worker.issueRegistrationToken", aggregateType: "account", aggregateId: subjectId, projectId: null }, async (policy) => {
5580
+ await require2(actor, "account.manage", { type: "account" }, policy);
5581
+ const issued = unwrap(await write2.issueWorkerRegistrationToken({ owner: accountActorOf(actor) }));
5582
+ return { value: { secret: issued.secret, expiresAt: toTimestamp(issued.expiresAt) }, replayed: false };
5583
+ });
5584
+ },
5575
5585
  async reportInitStatus(actor, command) {
5576
5586
  const { projectId, phase } = command;
5577
5587
  return await audited(actor, { operationId: "project.reportInitStatus", aggregateType: "project", aggregateId: projectId, projectId, data: { phase, failed: command.error !== void 0 } }, async (policy) => {
@@ -10972,7 +10982,20 @@ var init_auth = __esm({
10972
10982
  });
10973
10983
 
10974
10984
  // src/sandbox-reg-tokens.ts
10985
+ import { createHash as createHash2 } from "node:crypto";
10975
10986
  import { nanoid as nanoid7 } from "nanoid";
10987
+ function hashSandboxRegToken(secret) {
10988
+ return createHash2("sha256").update(secret, "utf8").digest("hex");
10989
+ }
10990
+ async function issueEphemeralSandboxRegToken(db, email, ttlMs = EPHEMERAL_REG_TOKEN_TTL_MS) {
10991
+ const normalized = email.trim().toLowerCase();
10992
+ const secret = `sbxreg_${nanoid7(48)}`;
10993
+ const now = Date.now();
10994
+ const expiresAt = now + ttlMs;
10995
+ await db.run(`INSERT INTO sandbox_reg_tokens (token_hash, email, user_id, created_at, expires_at)
10996
+ VALUES (?, ?, (SELECT id FROM users WHERE email = ?), ?, ?)`, hashSandboxRegToken(secret), normalized, normalized, now, expiresAt);
10997
+ return { secret, expiresAt };
10998
+ }
10976
10999
  var EPHEMERAL_REG_TOKEN_TTL_MS;
10977
11000
  var init_sandbox_reg_tokens = __esm({
10978
11001
  "src/sandbox-reg-tokens.ts"() {
@@ -11268,6 +11291,25 @@ var init_state_machine2 = __esm({
11268
11291
  });
11269
11292
 
11270
11293
  // src/abuse-throttle.ts
11294
+ function createAbuseThrottle(opts) {
11295
+ const hits = /* @__PURE__ */ new Map();
11296
+ return {
11297
+ take(key) {
11298
+ const now = Date.now();
11299
+ const entry = hits.get(key);
11300
+ if (!entry || entry.resetAt <= now) {
11301
+ hits.set(key, { count: 1, resetAt: now + opts.windowMs });
11302
+ return true;
11303
+ }
11304
+ if (entry.count >= opts.max) return false;
11305
+ entry.count += 1;
11306
+ return true;
11307
+ },
11308
+ reset() {
11309
+ hits.clear();
11310
+ }
11311
+ };
11312
+ }
11271
11313
  var init_abuse_throttle = __esm({
11272
11314
  "src/abuse-throttle.ts"() {
11273
11315
  "use strict";
@@ -11298,11 +11340,11 @@ var init_sandbox2 = __esm({
11298
11340
  init_sealedbox();
11299
11341
  init_abuse_throttle();
11300
11342
  init_auth();
11301
- init_cli_auth();
11302
11343
  init_sandbox_reg_tokens();
11303
11344
  init_users();
11304
11345
  init_agent_auth_probe();
11305
11346
  init_ws();
11347
+ init_route_registry();
11306
11348
  }
11307
11349
  });
11308
11350
 
@@ -11357,6 +11399,7 @@ var init_farm2 = __esm({
11357
11399
  init_sealedbox();
11358
11400
  init_abuse_throttle();
11359
11401
  init_ws();
11402
+ init_route_registry();
11360
11403
  }
11361
11404
  });
11362
11405
 
@@ -11389,6 +11432,7 @@ var init_runner2 = __esm({
11389
11432
  init_users();
11390
11433
  init_runner_reg_tokens();
11391
11434
  init_ws();
11435
+ init_route_registry();
11392
11436
  }
11393
11437
  });
11394
11438
 
@@ -12367,10 +12411,10 @@ __export(regimen_config_exports, {
12367
12411
  loadWorkflowSettings: () => loadWorkflowSettings,
12368
12412
  loadYouTrackWrites: () => loadYouTrackWrites
12369
12413
  });
12370
- import { createHash as createHash2 } from "node:crypto";
12414
+ import { createHash as createHash3 } from "node:crypto";
12371
12415
  import { parse as parseYaml2 } from "yaml";
12372
12416
  function contentSha2(content) {
12373
- return createHash2("sha1").update(content, "utf-8").digest("hex");
12417
+ return createHash3("sha1").update(content, "utf-8").digest("hex");
12374
12418
  }
12375
12419
  async function applyRegimenContent(db, projectId, content, log, preParsed) {
12376
12420
  const previous = cache3.get(projectId) ?? null;
@@ -19186,9 +19230,9 @@ var init_cluster_order = __esm({
19186
19230
  });
19187
19231
 
19188
19232
  // src/prompts/prompt-cache.ts
19189
- import { createHash as createHash3 } from "node:crypto";
19233
+ import { createHash as createHash4 } from "node:crypto";
19190
19234
  function contentSha3(content) {
19191
- return createHash3("sha1").update(content, "utf-8").digest("hex");
19235
+ return createHash4("sha1").update(content, "utf-8").digest("hex");
19192
19236
  }
19193
19237
  function getProjectPromptTemplate(projectId, role) {
19194
19238
  return cache4.get(projectId)?.get(role)?.content ?? null;
@@ -22786,7 +22830,7 @@ __export(lifecycle_exports, {
22786
22830
  taskRunsDirectImplement: () => taskRunsDirectImplement,
22787
22831
  triggerRework: () => triggerRework
22788
22832
  });
22789
- import { createHash as createHash4 } from "node:crypto";
22833
+ import { createHash as createHash5 } from "node:crypto";
22790
22834
  async function reconcileStuckTasks(db, log) {
22791
22835
  let recovered = 0;
22792
22836
  for (const task of await listTasks(db)) {
@@ -23381,7 +23425,7 @@ async function maybeAwaitingAnswer(db, task, log, blockingQuestions = []) {
23381
23425
  if (blocking.length === 0) return false;
23382
23426
  const now = Date.now();
23383
23427
  for (let i = 0; i < blocking.length; i++) {
23384
- const digest2 = createHash4("sha256").update(blocking[i]).digest("hex").slice(0, 12);
23428
+ const digest2 = createHash5("sha256").update(blocking[i]).digest("hex").slice(0, 12);
23385
23429
  await ingestComment(db, {
23386
23430
  taskId: task.id,
23387
23431
  trackerCommentId: `agent:${task.phase.toLowerCase()}:${i}:${digest2}`,
@@ -28487,6 +28531,8 @@ function requestGhRunnersDelete(runnerId, projectId, sealedBundle, timeoutMs) {
28487
28531
  init_farm2();
28488
28532
  init_runner2();
28489
28533
  init_init_status();
28534
+ init_sandbox_reg_tokens();
28535
+ init_abuse_throttle();
28490
28536
 
28491
28537
  // src/projects/forget.ts
28492
28538
  init_legacy_cred_bundles();
@@ -28675,6 +28721,7 @@ function nodeUnavailable(capability, err) {
28675
28721
  function taskState(row) {
28676
28722
  return { ...row, revision: String(row.updatedAt) };
28677
28723
  }
28724
+ var WORKER_REG_TOKEN_MINTS = createAbuseThrottle({ windowMs: 60 * 6e4, max: 30 });
28678
28725
  function createWriteModel(db, log, hooks = {}) {
28679
28726
  async function readTaskRow(projectId, key) {
28680
28727
  const row = await db.get(
@@ -29357,6 +29404,16 @@ function createWriteModel(db, log, hooks = {}) {
29357
29404
  log.info({ projectId, jobId, kind }, kind === "backfill" ? "backfill job enqueued" : "index job enqueued");
29358
29405
  return ok({ jobId });
29359
29406
  },
29407
+ async issueWorkerRegistrationToken({ owner }) {
29408
+ if (owner.email === null) return { ok: false, refusal: "unprocessable", reason: "account_has_no_email" };
29409
+ if (!WORKER_REG_TOKEN_MINTS.take(`email:${owner.email}`)) {
29410
+ log.warn({ email: owner.email }, "ephemeral sandbox reg token mint throttled");
29411
+ return { ok: false, refusal: "rate_limited" };
29412
+ }
29413
+ const issued = await issueEphemeralSandboxRegToken(db, owner.email);
29414
+ log.info({ email: owner.email }, "ephemeral sandbox reg token issued for a client");
29415
+ return ok(issued);
29416
+ },
29360
29417
  async reportInitStatus({ projectId, phase, error }) {
29361
29418
  const recorded = await advanceInitPhase(db, projectId, phase);
29362
29419
  if (error !== null) {
package/hub/dist/index.js CHANGED
@@ -13,7 +13,7 @@ var PLATFORM_VERSION;
13
13
  var init_version_generated = __esm({
14
14
  "../packages/shared/dist/version.generated.js"() {
15
15
  "use strict";
16
- PLATFORM_VERSION = "0.14.1-dev.422.gf90f06c4";
16
+ PLATFORM_VERSION = "0.14.1-dev.423.ga49d1f61";
17
17
  }
18
18
  });
19
19
 
@@ -31,8 +31,8 @@ var init_version = __esm({
31
31
  "../packages/shared/dist/version.js"() {
32
32
  "use strict";
33
33
  init_version_generated();
34
- PROTOCOL_VERSION = 4;
35
- MIN_SUPPORTED_PROTOCOL = 4;
34
+ PROTOCOL_VERSION = 5;
35
+ MIN_SUPPORTED_PROTOCOL = 5;
36
36
  }
37
37
  });
38
38
 
@@ -2960,21 +2960,14 @@ var init_duration = __esm({
2960
2960
  });
2961
2961
 
2962
2962
  // ../packages/shared/dist/node-handshake.js
2963
- function readNodeSocketSecrets(headers, query) {
2964
- const found = [];
2963
+ function readNodeSocketSecret(headers) {
2965
2964
  const raw = headers["authorization"];
2966
2965
  const header = Array.isArray(raw) ? raw[0] : raw;
2967
- if (typeof header === "string") {
2968
- const match = /^Bearer\s+(.+)$/i.exec(header.trim());
2969
- const token = match?.[1]?.trim();
2970
- if (token !== void 0 && token !== "")
2971
- found.push({ token, source: "header" });
2972
- }
2973
- const fromQuery = query?.token;
2974
- if (typeof fromQuery === "string" && fromQuery !== "") {
2975
- found.push({ token: fromQuery, source: "query" });
2976
- }
2977
- return found;
2966
+ if (typeof header !== "string")
2967
+ return null;
2968
+ const match = /^Bearer\s+(.+)$/i.exec(header.trim());
2969
+ const token = match?.[1]?.trim();
2970
+ return token !== void 0 && token !== "" ? token : null;
2978
2971
  }
2979
2972
  function decideNodeHandshake(frame, hub, recognizedCapabilities, authenticatedAs) {
2980
2973
  const handshake = parseNodeHandshake(frame);
@@ -8716,7 +8709,12 @@ var init_route_definition = __esm({
8716
8709
  "bootstrap.request",
8717
8710
  "bootstrap.confirm",
8718
8711
  "bootstrap.authorizeEmail",
8719
- "bootstrap.authorizeConfirm"
8712
+ "bootstrap.authorizeConfirm",
8713
+ // DEV-927: a node registers with a registration token in the body — the
8714
+ // token IS the credential, and there is no node identity yet to bear it.
8715
+ "node.sandbox.register",
8716
+ "node.farm.register",
8717
+ "node.runner.register"
8720
8718
  ];
8721
8719
  }
8722
8720
  });
@@ -12239,6 +12237,8 @@ function createCommandOperations(deps) {
12239
12237
  throw errors.unprocessable({ details: { reason: outcome.reason } });
12240
12238
  case "not_found":
12241
12239
  throw errors.notFound();
12240
+ case "rate_limited":
12241
+ throw errors.rateLimited();
12242
12242
  }
12243
12243
  }
12244
12244
  function accountActorOf(actor) {
@@ -12828,6 +12828,14 @@ function createCommandOperations(deps) {
12828
12828
  return { value: run.result, replayed: run.replayed };
12829
12829
  });
12830
12830
  },
12831
+ async issueWorkerRegistrationToken(actor) {
12832
+ const subjectId = actor.principal.subjectId;
12833
+ return await audited(actor, { operationId: "worker.issueRegistrationToken", aggregateType: "account", aggregateId: subjectId, projectId: null }, async (policy) => {
12834
+ await require2(actor, "account.manage", { type: "account" }, policy);
12835
+ const issued = unwrap(await write2.issueWorkerRegistrationToken({ owner: accountActorOf(actor) }));
12836
+ return { value: { secret: issued.secret, expiresAt: toTimestamp(issued.expiresAt) }, replayed: false };
12837
+ });
12838
+ },
12831
12839
  async reportInitStatus(actor, command) {
12832
12840
  const { projectId, phase } = command;
12833
12841
  return await audited(actor, { operationId: "project.reportInitStatus", aggregateType: "project", aggregateId: projectId, projectId, data: { phase, failed: command.error !== void 0 } }, async (policy) => {
@@ -13125,8 +13133,8 @@ var init_cutover_preflight = __esm({
13125
13133
  SECRET_CONSUMERS = [
13126
13134
  { consumer: "agent credential in the job bundle (Claude / Codex)", purpose: "agent_execution", implemented: true, note: "issued per job from the project's auth mode" },
13127
13135
  { consumer: "repository clone tokens in the job bundle (GitHub / GitLab / Bitbucket)", purpose: "repository_read", implemented: true, note: "one grant for each host the job clones" },
13128
- { consumer: "Context Repo push of the delivery from the SANDBOX (contextWrite)", purpose: "context_write", implemented: false, note: "open: the delivery has to reach the holder of the materializer role without a write credential on the SANDBOX \u2014 the transport is an owner's decision" },
13129
- { consumer: "mirror clone token in the job bundle (mirrorWrite, read use)", purpose: "repository_read", implemented: false, note: "open: a read grant under the mirror's host, issued with the delivery transport" },
13136
+ { consumer: "Context Repo push of the delivery from the SANDBOX (contextWrite)", purpose: "context_write", implemented: true, note: "the SANDBOX publishes the delivery over the host-local relay and the holder of the materializer role pushes it; no write credential on the SANDBOX" },
13137
+ { consumer: "mirror clone token in the job bundle (mirrorWrite, read use)", purpose: "repository_read", implemented: true, note: "a read grant under the mirror's host, issued by the holder of the role with the other repository grants" },
13130
13138
  { consumer: "tracker writes (YouTrack) \u2014 CLI-side, never on the SANDBOX", purpose: "tracker_write", implemented: true, note: "nothing to move: the holder of the role already writes" },
13131
13139
  { consumer: "named project secrets in custom-action runs \u2014 CLI-side", purpose: "custom_action", implemented: true, note: "nothing to move until a tool-run executes on the SANDBOX" }
13132
13140
  ];
@@ -25805,10 +25813,16 @@ async function deleteSandboxRegistration(db, sandboxId) {
25805
25813
  async function sandboxRoutes(app, deps) {
25806
25814
  const { db, config } = deps;
25807
25815
  const registerByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 30 });
25808
- const regTokenMintByEmail = createAbuseThrottle({ windowMs: 60 * 6e4, max: 30 });
25809
25816
  app.post(
25810
- "/sandbox/register",
25817
+ "/node/v1/sandbox/register",
25811
25818
  {
25819
+ ...routeMeta({
25820
+ surface: "node",
25821
+ operationId: "node.sandbox.register",
25822
+ authPolicy: "public",
25823
+ stability: "preview",
25824
+ summary: "Register a SANDBOX with a registration token"
25825
+ }),
25812
25826
  schema: {
25813
25827
  body: {
25814
25828
  type: "object",
@@ -25849,14 +25863,29 @@ async function sandboxRoutes(app, deps) {
25849
25863
  return { sandboxId: id, token };
25850
25864
  }
25851
25865
  );
25852
- app.get("/sandbox/me", async (req, reply) => {
25866
+ app.get("/node/v1/sandbox/me", routeMeta({
25867
+ surface: "node",
25868
+ operationId: "node.sandbox.me",
25869
+ authPolicy: "node_actor",
25870
+ resourcePolicy: "node.connect",
25871
+ stability: "preview",
25872
+ summary: "Is this SANDBOX registration still known"
25873
+ }), async (req, reply) => {
25853
25874
  const sandbox = await authenticateSandboxByBearer(db, req.headers.authorization);
25854
25875
  if (!sandbox) return reply.status(401).send({ error: "unauthorized" });
25855
25876
  return { sandboxId: sandbox.id, name: sandbox.name };
25856
25877
  });
25857
25878
  app.post(
25858
- "/sandbox/agent-auth",
25879
+ "/node/v1/sandbox/agent-auth",
25859
25880
  {
25881
+ ...routeMeta({
25882
+ surface: "node",
25883
+ operationId: "node.sandbox.agentAuth",
25884
+ authPolicy: "node_actor",
25885
+ resourcePolicy: "node.connect",
25886
+ stability: "preview",
25887
+ summary: "Report the host's agent login and ask whether to probe it"
25888
+ }),
25860
25889
  schema: {
25861
25890
  body: {
25862
25891
  type: "object",
@@ -25892,8 +25921,16 @@ async function sandboxRoutes(app, deps) {
25892
25921
  }
25893
25922
  );
25894
25923
  app.post(
25895
- "/sandbox/agent-auth/result",
25924
+ "/node/v1/sandbox/agent-auth/result",
25896
25925
  {
25926
+ ...routeMeta({
25927
+ surface: "node",
25928
+ operationId: "node.sandbox.agentAuthResult",
25929
+ authPolicy: "node_actor",
25930
+ resourcePolicy: "node.connect",
25931
+ stability: "preview",
25932
+ summary: "Report the verdict of an agent login probe"
25933
+ }),
25897
25934
  schema: {
25898
25935
  body: {
25899
25936
  type: "object",
@@ -25943,19 +25980,6 @@ async function sandboxRoutes(app, deps) {
25943
25980
  app.log.info({ email: session.email, revoked }, "sandbox reg token revoked");
25944
25981
  return { revoked };
25945
25982
  });
25946
- app.post("/sandbox/reg-token/cli", async (req, reply) => {
25947
- const auth = await resolveCliAuth(db, config, req.headers.authorization);
25948
- if (!auth) {
25949
- return reply.status(401).send({ error: "bad_cli_token" });
25950
- }
25951
- if (!regTokenMintByEmail.take(`email:${auth.email}`)) {
25952
- app.log.warn({ email: auth.email }, "ephemeral sandbox reg token mint throttled");
25953
- return reply.status(429).send({ error: "too_many_requests" });
25954
- }
25955
- const { secret, expiresAt } = await issueEphemeralSandboxRegToken(db, auth.email);
25956
- app.log.info({ email: auth.email }, "ephemeral sandbox reg token issued for CLI");
25957
- return { secret, expiresAt };
25958
- });
25959
25983
  app.delete(
25960
25984
  "/sandbox/:sandboxId",
25961
25985
  async (req, reply) => {
@@ -25992,11 +26016,11 @@ var init_sandbox2 = __esm({
25992
26016
  init_sealedbox();
25993
26017
  init_abuse_throttle();
25994
26018
  init_auth();
25995
- init_cli_auth();
25996
26019
  init_sandbox_reg_tokens();
25997
26020
  init_users();
25998
26021
  init_agent_auth_probe();
25999
26022
  init_ws();
26023
+ init_route_registry();
26000
26024
  }
26001
26025
  });
26002
26026
 
@@ -26060,8 +26084,15 @@ async function farmRoutes(app, deps) {
26060
26084
  const { db, config } = deps;
26061
26085
  const registerByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 10 });
26062
26086
  app.post(
26063
- "/farm/register",
26087
+ "/node/v1/farm/register",
26064
26088
  {
26089
+ ...routeMeta({
26090
+ surface: "node",
26091
+ operationId: "node.farm.register",
26092
+ authPolicy: "public",
26093
+ stability: "preview",
26094
+ summary: "Register a FARM with the operator's registration token"
26095
+ }),
26065
26096
  schema: {
26066
26097
  body: {
26067
26098
  type: "object",
@@ -26140,6 +26171,7 @@ var init_farm2 = __esm({
26140
26171
  init_sealedbox();
26141
26172
  init_abuse_throttle();
26142
26173
  init_ws();
26174
+ init_route_registry();
26143
26175
  }
26144
26176
  });
26145
26177
 
@@ -26241,8 +26273,15 @@ async function runnerRoutes(app, deps) {
26241
26273
  const { db, config } = deps;
26242
26274
  const registerByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 10 });
26243
26275
  app.post(
26244
- "/runner/register",
26276
+ "/node/v1/runner/register",
26245
26277
  {
26278
+ ...routeMeta({
26279
+ surface: "node",
26280
+ operationId: "node.runner.register",
26281
+ authPolicy: "public",
26282
+ stability: "preview",
26283
+ summary: "Register a RUNNER host with a personal registration token"
26284
+ }),
26246
26285
  schema: {
26247
26286
  body: {
26248
26287
  type: "object",
@@ -26345,6 +26384,7 @@ var init_runner2 = __esm({
26345
26384
  init_users();
26346
26385
  init_runner_reg_tokens();
26347
26386
  init_ws();
26387
+ init_route_registry();
26348
26388
  }
26349
26389
  });
26350
26390
 
@@ -27524,23 +27564,11 @@ function installSocketKeepalive(socket, periodMs) {
27524
27564
  }
27525
27565
  async function wsRoutes(app, deps) {
27526
27566
  const { db, config } = deps;
27527
- async function authenticateSocket(req, component, resolve4) {
27528
- for (const secret of readNodeSocketSecrets(req.headers, req.query)) {
27529
- const resolved = await resolve4(secret.token);
27530
- if (resolved === null || resolved === void 0) continue;
27531
- if (secret.source === "query") warnAboutQueryCredential(component, req.ip ?? null);
27532
- return resolved;
27533
- }
27534
- return null;
27535
- }
27536
- const warnedComponents = /* @__PURE__ */ new Set();
27537
- function warnAboutQueryCredential(component, remote) {
27538
- if (warnedComponents.has(component)) return;
27539
- warnedComponents.add(component);
27540
- app.log.warn(
27541
- { component, remote },
27542
- "node socket authenticated from the query string \u2014 the credential lands in proxy logs; update the component to send it in the Authorization header (this is logged once)"
27543
- );
27567
+ async function authenticateSocket(req, resolve4) {
27568
+ const token = readNodeSocketSecret(req.headers);
27569
+ if (token === null) return null;
27570
+ const resolved = await resolve4(token);
27571
+ return resolved === null || resolved === void 0 ? null : resolved;
27544
27572
  }
27545
27573
  const cliSocket = async (socket, req) => {
27546
27574
  let boundProjectId = null;
@@ -27877,7 +27905,7 @@ async function wsRoutes(app, deps) {
27877
27905
  app.log.info({ remote: req.ip }, "CLI socket closed (never registered)");
27878
27906
  }
27879
27907
  });
27880
- auth = await authenticateSocket(req, "cli", (token) => authFromSecret(db, config, token));
27908
+ auth = await authenticateSocket(req, (token) => authFromSecret(db, config, token));
27881
27909
  if (!auth) {
27882
27910
  socket.close(1008, "unauthorized");
27883
27911
  return;
@@ -27895,7 +27923,7 @@ async function wsRoutes(app, deps) {
27895
27923
  void pump();
27896
27924
  };
27897
27925
  const sandboxSocket = async (socket, req) => {
27898
- const sandbox = await authenticateSocket(req, "sandbox", (token) => findSandboxByToken(db, token));
27926
+ const sandbox = await authenticateSocket(req, (token) => findSandboxByToken(db, token));
27899
27927
  if (!sandbox) {
27900
27928
  socket.close(1008, "unauthorized");
27901
27929
  return;
@@ -28013,7 +28041,7 @@ async function wsRoutes(app, deps) {
28013
28041
  });
28014
28042
  };
28015
28043
  const farmSocket = async (socket, req) => {
28016
- const farm = await authenticateSocket(req, "farm", (token) => findFarmByToken(db, token));
28044
+ const farm = await authenticateSocket(req, (token) => findFarmByToken(db, token));
28017
28045
  if (!farm) {
28018
28046
  socket.close(1008, "unauthorized");
28019
28047
  return;
@@ -28105,7 +28133,7 @@ async function wsRoutes(app, deps) {
28105
28133
  });
28106
28134
  };
28107
28135
  const runnerSocket = async (socket, req) => {
28108
- const runner = await authenticateSocket(req, "runner", (token) => findRunnerByToken(db, token));
28136
+ const runner = await authenticateSocket(req, (token) => findRunnerByToken(db, token));
28109
28137
  if (!runner) {
28110
28138
  socket.close(1008, "unauthorized");
28111
28139
  return;
@@ -28237,10 +28265,12 @@ async function wsRoutes(app, deps) {
28237
28265
  }
28238
28266
  const auth = parseNodeAuth(parsed);
28239
28267
  if (auth !== null) {
28240
- if (auth.component !== component || !await authenticate({ ...req, headers: { authorization: `Bearer ${auth.token}` } })) {
28268
+ const withFrameCredential = { ...req, headers: { ...req.headers, authorization: `Bearer ${auth.token}` } };
28269
+ if (auth.component !== component || !await authenticate(withFrameCredential)) {
28241
28270
  refuse("unauthenticated");
28242
28271
  return;
28243
28272
  }
28273
+ effectiveReq = withFrameCredential;
28244
28274
  authenticated = true;
28245
28275
  return;
28246
28276
  }
@@ -28277,7 +28307,7 @@ async function wsRoutes(app, deps) {
28277
28307
  socket.off("message", listener);
28278
28308
  socket.pause();
28279
28309
  try {
28280
- await inner(socket, req);
28310
+ await inner(socket, effectiveReq);
28281
28311
  } catch (err) {
28282
28312
  app.log.error({ err, component }, "node socket handler failed after handshake");
28283
28313
  socket.close(1011, "handler_failed");
@@ -28288,6 +28318,7 @@ async function wsRoutes(app, deps) {
28288
28318
  socket.resume();
28289
28319
  };
28290
28320
  let authenticated = false;
28321
+ let effectiveReq = req;
28291
28322
  const ready = authenticate(req).then((ok2) => {
28292
28323
  authenticated = ok2;
28293
28324
  });
@@ -28321,7 +28352,6 @@ async function wsRoutes(app, deps) {
28321
28352
  });
28322
28353
  }
28323
28354
  }
28324
- app.get("/cli/connect", { websocket: true }, cliSocket);
28325
28355
  app.get(
28326
28356
  "/node/v1/cli/connect",
28327
28357
  {
@@ -28335,9 +28365,8 @@ async function wsRoutes(app, deps) {
28335
28365
  summary: "Control socket of a CLI instance"
28336
28366
  })
28337
28367
  },
28338
- withNodeHandshake("cli", async (r) => await authenticateSocket(r, "cli", (token) => authFromSecret(db, config, token)) !== null, cliSocket)
28368
+ withNodeHandshake("cli", async (r) => await authenticateSocket(r, (token) => authFromSecret(db, config, token)) !== null, cliSocket)
28339
28369
  );
28340
- app.get("/sandbox/connect", { websocket: true }, sandboxSocket);
28341
28370
  app.get(
28342
28371
  "/node/v1/sandbox/connect",
28343
28372
  {
@@ -28351,9 +28380,8 @@ async function wsRoutes(app, deps) {
28351
28380
  summary: "Control socket of a SANDBOX instance"
28352
28381
  })
28353
28382
  },
28354
- withNodeHandshake("sandbox", async (r) => await authenticateSocket(r, "sandbox", (token) => findSandboxByToken(db, token)) !== null, sandboxSocket)
28383
+ withNodeHandshake("sandbox", async (r) => await authenticateSocket(r, (token) => findSandboxByToken(db, token)) !== null, sandboxSocket)
28355
28384
  );
28356
- app.get("/farm/connect", { websocket: true }, farmSocket);
28357
28385
  app.get(
28358
28386
  "/node/v1/farm/connect",
28359
28387
  {
@@ -28367,9 +28395,8 @@ async function wsRoutes(app, deps) {
28367
28395
  summary: "Control socket of a FARM instance"
28368
28396
  })
28369
28397
  },
28370
- withNodeHandshake("farm", async (r) => await authenticateSocket(r, "farm", (token) => findFarmByToken(db, token)) !== null, farmSocket)
28398
+ withNodeHandshake("farm", async (r) => await authenticateSocket(r, (token) => findFarmByToken(db, token)) !== null, farmSocket)
28371
28399
  );
28372
- app.get("/runner/connect", { websocket: true }, runnerSocket);
28373
28400
  app.get(
28374
28401
  "/node/v1/runner/connect",
28375
28402
  {
@@ -28383,7 +28410,7 @@ async function wsRoutes(app, deps) {
28383
28410
  summary: "Control socket of a RUNNER instance"
28384
28411
  })
28385
28412
  },
28386
- withNodeHandshake("runner", async (r) => await authenticateSocket(r, "runner", (token) => findRunnerByToken(db, token)) !== null, runnerSocket)
28413
+ withNodeHandshake("runner", async (r) => await authenticateSocket(r, (token) => findRunnerByToken(db, token)) !== null, runnerSocket)
28387
28414
  );
28388
28415
  }
28389
28416
  var agentLoginProviders, deliveryFollowers, projectCliConnections, projectLastSeenAt, projectLastDuplicateRegisterAt, sandboxConnections, farmConnections, runnerConnections, cliMessageHandlers, cliRegisterHandlers, cliDisconnectHandlers, farmMessageHandlers, runnerMessageHandlers, providerMessageHandlers, RECOGNIZED_NODE_CAPABILITIES, NODE_HANDSHAKE_BACKLOG, NODE_HANDSHAKE_TIMEOUT_MS;
@@ -28978,9 +29005,22 @@ var RETIRED_SOCKET_PATHS = [
28978
29005
  "/farm/connect",
28979
29006
  "/runner/connect"
28980
29007
  ];
29008
+ var RETIRED_NODE_PATHS = [
29009
+ "/jobs/claim",
29010
+ "/jobs/heartbeat",
29011
+ "/jobs/release",
29012
+ "/jobs/result",
29013
+ "/sandbox/register",
29014
+ "/sandbox/me",
29015
+ "/sandbox/agent-auth",
29016
+ "/sandbox/agent-auth/result",
29017
+ "/sandbox/reg-token/cli",
29018
+ "/farm/register",
29019
+ "/runner/register"
29020
+ ];
28981
29021
  function isRetiredClientPath(url, registeredPaths = []) {
28982
29022
  const path = url.split("?")[0].split("#")[0];
28983
- if (RETIRED_SOCKET_PATHS.includes(path)) return !registeredPaths.includes(path);
29023
+ if (RETIRED_SOCKET_PATHS.includes(path) || RETIRED_NODE_PATHS.includes(path)) return !registeredPaths.includes(path);
28984
29024
  const retired = RETIRED_ROOTS.find((root) => path === root || path.startsWith(`${root}/`));
28985
29025
  if (retired === void 0) return false;
28986
29026
  return !registeredPaths.some((p) => p === retired || p.startsWith(`${retired}/`));
@@ -31024,6 +31064,28 @@ async function apiV1MutationRoutes(app, deps) {
31024
31064
  }
31025
31065
  }
31026
31066
  );
31067
+ app.post(
31068
+ "/workers/registration-token",
31069
+ routeMeta({
31070
+ surface: "application",
31071
+ operationId: "worker.issueRegistrationToken",
31072
+ authPolicy: "application_actor",
31073
+ resourcePolicy: "account.manage",
31074
+ stability: "preview",
31075
+ summary: "Mint a single-use registration token for a worker of this account"
31076
+ }),
31077
+ async (req, reply) => {
31078
+ const requestId = requestIdOf(req);
31079
+ try {
31080
+ const actor = await actorOr401({ db, config }, req, reply, requestId);
31081
+ if (!actor) return reply;
31082
+ const issued = await commands.issueWorkerRegistrationToken(actor);
31083
+ return reply.status(201).send(issued);
31084
+ } catch (err) {
31085
+ return sendError(reply, err, requestId);
31086
+ }
31087
+ }
31088
+ );
31027
31089
  app.post(
31028
31090
  "/projects/:projectId/init-status",
31029
31091
  routeMeta({
@@ -34145,6 +34207,8 @@ function requestGhRunnersDelete(runnerId, projectId, sealedBundle, timeoutMs) {
34145
34207
  init_farm2();
34146
34208
  init_runner2();
34147
34209
  init_init_status();
34210
+ init_sandbox_reg_tokens();
34211
+ init_abuse_throttle();
34148
34212
 
34149
34213
  // src/projects/forget.ts
34150
34214
  init_legacy_cred_bundles();
@@ -34426,6 +34490,7 @@ function nodeUnavailable(capability, err) {
34426
34490
  function taskState(row) {
34427
34491
  return { ...row, revision: String(row.updatedAt) };
34428
34492
  }
34493
+ var WORKER_REG_TOKEN_MINTS = createAbuseThrottle({ windowMs: 60 * 6e4, max: 30 });
34429
34494
  function createWriteModel(db, log, hooks = {}) {
34430
34495
  async function readTaskRow(projectId, key) {
34431
34496
  const row = await db.get(
@@ -35108,6 +35173,16 @@ function createWriteModel(db, log, hooks = {}) {
35108
35173
  log.info({ projectId, jobId, kind }, kind === "backfill" ? "backfill job enqueued" : "index job enqueued");
35109
35174
  return ok({ jobId });
35110
35175
  },
35176
+ async issueWorkerRegistrationToken({ owner }) {
35177
+ if (owner.email === null) return { ok: false, refusal: "unprocessable", reason: "account_has_no_email" };
35178
+ if (!WORKER_REG_TOKEN_MINTS.take(`email:${owner.email}`)) {
35179
+ log.warn({ email: owner.email }, "ephemeral sandbox reg token mint throttled");
35180
+ return { ok: false, refusal: "rate_limited" };
35181
+ }
35182
+ const issued = await issueEphemeralSandboxRegToken(db, owner.email);
35183
+ log.info({ email: owner.email }, "ephemeral sandbox reg token issued for a client");
35184
+ return ok(issued);
35185
+ },
35111
35186
  async reportInitStatus({ projectId, phase, error }) {
35112
35187
  const recorded = await advanceInitPhase(db, projectId, phase);
35113
35188
  if (error !== null) {
@@ -39522,10 +39597,18 @@ async function negotiateJobCredentials(db, spec, workerId, log, issuer) {
39522
39597
  // src/routes/jobs.ts
39523
39598
  init_sandbox2();
39524
39599
  init_ws();
39600
+ init_route_registry();
39525
39601
  async function jobsRoutes(app, deps) {
39526
39602
  const { db, config } = deps;
39527
39603
  const notifyDeps = deps.notify;
39528
- app.post("/jobs/claim", async (req, reply) => {
39604
+ app.post("/node/v1/jobs/claim", routeMeta({
39605
+ surface: "node",
39606
+ operationId: "node.jobs.claim",
39607
+ authPolicy: "node_actor",
39608
+ resourcePolicy: "node.connect",
39609
+ stability: "preview",
39610
+ summary: "Claim the next job for this SANDBOX (long poll)"
39611
+ }), async (req, reply) => {
39529
39612
  const sandbox = await authenticateSandboxByBearer(db, req.headers.authorization);
39530
39613
  if (!sandbox) {
39531
39614
  return reply.status(401).send({ error: "unauthorized" });
@@ -39579,8 +39662,16 @@ async function jobsRoutes(app, deps) {
39579
39662
  }
39580
39663
  });
39581
39664
  app.post(
39582
- "/jobs/heartbeat",
39665
+ "/node/v1/jobs/heartbeat",
39583
39666
  {
39667
+ ...routeMeta({
39668
+ surface: "node",
39669
+ operationId: "node.jobs.heartbeat",
39670
+ authPolicy: "node_actor",
39671
+ resourcePolicy: "node.connect",
39672
+ stability: "preview",
39673
+ summary: "Keep a claimed job's lease alive"
39674
+ }),
39584
39675
  schema: {
39585
39676
  body: {
39586
39677
  type: "object",
@@ -39642,8 +39733,16 @@ async function jobsRoutes(app, deps) {
39642
39733
  }
39643
39734
  );
39644
39735
  app.post(
39645
- "/jobs/release",
39736
+ "/node/v1/jobs/release",
39646
39737
  {
39738
+ ...routeMeta({
39739
+ surface: "node",
39740
+ operationId: "node.jobs.release",
39741
+ authPolicy: "node_actor",
39742
+ resourcePolicy: "node.connect",
39743
+ stability: "preview",
39744
+ summary: "Hand a claimed job back to the queue"
39745
+ }),
39647
39746
  schema: {
39648
39747
  body: {
39649
39748
  type: "object",
@@ -39679,8 +39778,16 @@ async function jobsRoutes(app, deps) {
39679
39778
  }
39680
39779
  );
39681
39780
  app.post(
39682
- "/jobs/result",
39781
+ "/node/v1/jobs/result",
39683
39782
  {
39783
+ ...routeMeta({
39784
+ surface: "node",
39785
+ operationId: "node.jobs.result",
39786
+ authPolicy: "node_actor",
39787
+ resourcePolicy: "node.connect",
39788
+ stability: "preview",
39789
+ summary: "Report a finished job"
39790
+ }),
39684
39791
  schema: {
39685
39792
  body: {
39686
39793
  type: "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-partner/codepipe-hub",
3
- "version": "0.14.1-dev.422.gf90f06c4",
3
+ "version": "0.14.1-dev.423.ga49d1f61",
4
4
  "description": "The CodePipe hub, packaged for one machine: the CLI installs it on demand for the local profile.",
5
5
  "type": "module",
6
6
  "main": "./hub/dist/index.js",