@code-partner/codepipe-hub 0.14.1-dev.423.ga49d1f61 → 0.14.1-dev.424.ge3c8cc77

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.423.ga49d1f61";
460
+ PLATFORM_VERSION = "0.14.1-dev.424.ge3c8cc77";
461
461
  }
462
462
  });
463
463
 
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.423.ga49d1f61";
16
+ PLATFORM_VERSION = "0.14.1-dev.424.ge3c8cc77";
17
17
  }
18
18
  });
19
19
 
@@ -8675,7 +8675,10 @@ var init_route_definition = __esm({
8675
8675
  node: ["/node/v1"],
8676
8676
  local: ["/local"],
8677
8677
  mcp: ["/mcp"],
8678
- bootstrap: ["/auth", "/bootstrap"],
8678
+ // `/cli` is the CLI's own bootstrap: the connection request it opens, the
8679
+ // page and the email link a person confirms it on (DEV-927 settled it here
8680
+ // rather than moving a path the CLI and a sent email already hold).
8681
+ bootstrap: ["/auth", "/bootstrap", "/cli"],
8679
8682
  operational: ["/healthz", "/readyz"],
8680
8683
  // Static serves the browser bundle from the root and the install script from
8681
8684
  // its documented path (ADR-0060 §5), so it has no single prefix to check.
@@ -8697,7 +8700,8 @@ var init_route_definition = __esm({
8697
8700
  "/api/tasks",
8698
8701
  "/api/projects",
8699
8702
  "/api/admin",
8700
- "/api/notifications"
8703
+ "/api/notifications",
8704
+ "/bug-report"
8701
8705
  ];
8702
8706
  PUBLIC_MUTATION_ALLOWLIST = [
8703
8707
  "auth.login",
@@ -8705,11 +8709,8 @@ var init_route_definition = __esm({
8705
8709
  "auth.loginPassword",
8706
8710
  "auth.logout",
8707
8711
  "auth.register",
8708
- "auth.setPassword",
8709
8712
  "bootstrap.request",
8710
- "bootstrap.confirm",
8711
8713
  "bootstrap.authorizeEmail",
8712
- "bootstrap.authorizeConfirm",
8713
8714
  // DEV-927: a node registers with a registration token in the body — the
8714
8715
  // token IS the credential, and there is no node identity yet to bear it.
8715
8716
  "node.sandbox.register",
@@ -8720,8 +8721,8 @@ var init_route_definition = __esm({
8720
8721
  });
8721
8722
 
8722
8723
  // src/http/route-registry.ts
8723
- function installRouteGuard(app, mode, log) {
8724
- const registry = new RouteRegistry(mode, log);
8724
+ function installRouteGuard(app, mode, log, legacy = []) {
8725
+ const registry = new RouteRegistry(mode, log, legacy);
8725
8726
  app.addHook("onRoute", (route) => registry.inspect(route));
8726
8727
  return registry;
8727
8728
  }
@@ -8742,15 +8743,18 @@ var init_route_registry = __esm({
8742
8743
  init_route_definition();
8743
8744
  META_KEY = "codepipeRoute";
8744
8745
  RouteRegistry = class {
8745
- constructor(mode, log = { warn: (message) => console.warn(message) }) {
8746
+ constructor(mode, log = { warn: (message) => console.warn(message) }, legacy = []) {
8746
8747
  this.mode = mode;
8747
8748
  this.log = log;
8749
+ this.legacyAllowed = new Set(legacy);
8748
8750
  }
8749
8751
  routes = /* @__PURE__ */ new Map();
8750
8752
  paths = /* @__PURE__ */ new Set();
8751
8753
  operationIds = /* @__PURE__ */ new Set();
8752
8754
  unclassified = 0;
8753
8755
  invalid = 0;
8756
+ legacyAllowed;
8757
+ legacySeen = /* @__PURE__ */ new Set();
8754
8758
  /** Every route registered with metadata, in registration order. */
8755
8759
  list() {
8756
8760
  return [...this.routes.values()];
@@ -8768,6 +8772,12 @@ var init_route_registry = __esm({
8768
8772
  unclassifiedCount() {
8769
8773
  return this.unclassified;
8770
8774
  }
8775
+ /** The same registrations by name (`METHOD /path`), so a test can hold the
8776
+ * legacy list to exactly what the process still serves: an entry nobody
8777
+ * registers any more is a stale allowance, and it fails there. */
8778
+ unclassifiedRoutes() {
8779
+ return [...this.legacySeen];
8780
+ }
8771
8781
  /** Registrations that had metadata and broke a rule. Counted separately from
8772
8782
  * the unclassified ones: "not migrated yet" and "migrated wrongly" are
8773
8783
  * different problems, and a violation that lands in neither count is a
@@ -8787,12 +8797,14 @@ var init_route_registry = __esm({
8787
8797
  if (methods.length === 0) return;
8788
8798
  this.paths.add(route.url);
8789
8799
  if (!meta) {
8790
- this.unclassified += 1;
8791
- if (this.mode === "enforce") {
8800
+ const tolerated = methods.every((method) => this.legacyAllowed.has(`${method} ${route.url}`));
8801
+ if (this.mode === "enforce" && !tolerated) {
8792
8802
  throw new Error(
8793
- `route registry: ${methods.join("/")} ${route.url} registered without metadata \u2014 every route declares its surface, operationId and policy (ADR-0060 \xA73)`
8803
+ `route registry: ${methods.join("/")} ${route.url} registered without metadata \u2014 every route declares its surface, operationId and policy (ADR-0060 \xA73); the old roots are closed since the cutover (DEV-927)`
8794
8804
  );
8795
8805
  }
8806
+ this.unclassified += 1;
8807
+ for (const method of methods) this.legacySeen.add(`${method} ${route.url}`);
8796
8808
  return;
8797
8809
  }
8798
8810
  const violations = methods.flatMap(
@@ -28906,9 +28918,7 @@ var DASHBOARD_ROUTES = [
28906
28918
  var CONSOLE_ROUTES = [/^\/$/];
28907
28919
  var DECLARED_RESERVED_ROOTS = [
28908
28920
  ...Object.values(SURFACE_PREFIXES).flat(),
28909
- ...RETIRED_ROOTS,
28910
- "/cli",
28911
- "/ws"
28921
+ ...RETIRED_ROOTS
28912
28922
  ];
28913
28923
  function isBrowserRoute(app, path) {
28914
28924
  const clean = path.split("?")[0].split("#")[0];
@@ -29072,6 +29082,121 @@ async function registerSpaHosts(app, opts) {
29072
29082
  // src/server.ts
29073
29083
  init_route_registry();
29074
29084
 
29085
+ // src/http/legacy-routes.ts
29086
+ var STATIC_PLUGIN_ROUTES = ["GET /*"];
29087
+ var LEGACY_ROUTES = [
29088
+ "DELETE /api/admin/farms/:farmId",
29089
+ "DELETE /api/admin/runners/:runnerId",
29090
+ "DELETE /api/admin/sandboxes/:sandboxId",
29091
+ "DELETE /projects/:projectId/secrets/:name",
29092
+ "DELETE /runner/:runnerId",
29093
+ "DELETE /runner/reg-token",
29094
+ "DELETE /sandbox/:sandboxId",
29095
+ "DELETE /sandbox/reg-token",
29096
+ "GET /admin/pause",
29097
+ "GET /api/admin/farms",
29098
+ "GET /api/admin/logs",
29099
+ "GET /api/admin/presence",
29100
+ "GET /api/admin/projects",
29101
+ "GET /api/admin/registrations",
29102
+ "GET /api/admin/runners",
29103
+ "GET /api/admin/server",
29104
+ "GET /api/admin/settings",
29105
+ "GET /api/admin/stats",
29106
+ "GET /api/admin/users",
29107
+ "GET /api/notifications/settings",
29108
+ "GET /api/projects/:projectId/tasks/:taskKey/plan-review",
29109
+ "GET /bug-report",
29110
+ "GET /projects/:projectId/actions",
29111
+ "GET /projects/:projectId/adr-backfill",
29112
+ "GET /projects/:projectId/clusters/:anchorKey/journal",
29113
+ "GET /projects/:projectId/config-proposals/:proposalId/agent-activity/stream",
29114
+ "GET /projects/:projectId/context-file",
29115
+ "GET /projects/:projectId/drafts/:draftId/agent-activity/stream",
29116
+ "GET /projects/:projectId/model/drift",
29117
+ "GET /projects/:projectId/model/search",
29118
+ "GET /projects/:projectId/pause",
29119
+ "GET /projects/:projectId/prompts",
29120
+ "GET /projects/:projectId/prompts/:role",
29121
+ "GET /projects/:projectId/review-metrics",
29122
+ "GET /projects/:projectId/runs",
29123
+ "GET /projects/:projectId/runs/:runId/stream",
29124
+ "GET /projects/:projectId/tasks/:taskKey/actions",
29125
+ "GET /projects/:projectId/tasks/:taskKey/agent-activity/stream",
29126
+ "GET /projects/:projectId/tasks/:taskKey/external-review/draft",
29127
+ "GET /projects/:projectId/tasks/:taskKey/review-rounds",
29128
+ "GET /projects/:projectId/tracker-status",
29129
+ "GET /projects/:projectId/tracker/fields",
29130
+ "GET /runner/reg-token",
29131
+ "GET /runners",
29132
+ "GET /sandbox/reg-token",
29133
+ "POST /admin/pause",
29134
+ "POST /api/admin/farms/:farmId/assign",
29135
+ "POST /api/admin/farms/:farmId/unassign",
29136
+ "POST /api/admin/registrations/:id/approve",
29137
+ "POST /api/admin/registrations/:id/reject",
29138
+ "POST /api/admin/runners/:runnerId/owner",
29139
+ "POST /api/admin/users",
29140
+ "POST /api/admin/users/:email/activate",
29141
+ "POST /api/admin/users/:email/block",
29142
+ "POST /api/projects/:projectId/tasks/:taskKey/develop",
29143
+ "POST /api/projects/:projectId/tasks/:taskKey/observer-answer",
29144
+ "POST /api/projects/:projectId/tasks/:taskKey/plan-review",
29145
+ "POST /api/projects/:projectId/tasks/:taskKey/stage",
29146
+ "POST /api/projects/:projectId/tasks/:taskKey/workflow-route",
29147
+ "POST /bug-report",
29148
+ "POST /projects/:projectId/actions/:actionId/run",
29149
+ "POST /projects/:projectId/adr-backfill/discard",
29150
+ "POST /projects/:projectId/adr-backfill/promote",
29151
+ "POST /projects/:projectId/check-readiness",
29152
+ "POST /projects/:projectId/clusters/:anchorKey/reject",
29153
+ "POST /projects/:projectId/creds",
29154
+ "POST /projects/:projectId/detach-from-farm",
29155
+ "POST /projects/:projectId/gh-runner/connect",
29156
+ "POST /projects/:projectId/gh-runner/disconnect",
29157
+ "POST /projects/:projectId/model-bootstrap",
29158
+ "POST /projects/:projectId/move-to-farm",
29159
+ "POST /projects/:projectId/pause",
29160
+ "POST /projects/:projectId/refresh-context-status",
29161
+ "POST /projects/:projectId/retry-init",
29162
+ "POST /projects/:projectId/secrets",
29163
+ "POST /projects/:projectId/sync-config",
29164
+ "POST /projects/:projectId/sync-context",
29165
+ "POST /projects/:projectId/tasks/:taskKey/actions/:actionId/run",
29166
+ "POST /projects/:projectId/tasks/:taskKey/approve",
29167
+ "POST /projects/:projectId/tasks/:taskKey/build-flag",
29168
+ "POST /projects/:projectId/tasks/:taskKey/cancel",
29169
+ "POST /projects/:projectId/tasks/:taskKey/comment",
29170
+ "POST /projects/:projectId/tasks/:taskKey/external-review/close",
29171
+ "POST /projects/:projectId/tasks/:taskKey/external-review/dismiss",
29172
+ "POST /projects/:projectId/tasks/:taskKey/external-review/publish",
29173
+ "POST /projects/:projectId/tasks/:taskKey/external-review/rerun",
29174
+ "POST /projects/:projectId/tasks/:taskKey/external-review/start",
29175
+ "POST /projects/:projectId/tasks/:taskKey/force-done",
29176
+ "POST /projects/:projectId/tasks/:taskKey/open-pr",
29177
+ "POST /projects/:projectId/tasks/:taskKey/pause",
29178
+ "POST /projects/:projectId/tasks/:taskKey/publish-branch",
29179
+ "POST /projects/:projectId/tasks/:taskKey/reject",
29180
+ "POST /projects/:projectId/tasks/:taskKey/restart",
29181
+ "POST /projects/:projectId/tasks/:taskKey/retry",
29182
+ "POST /projects/:projectId/tasks/:taskKey/to-backlog",
29183
+ "POST /projects/:projectId/tasks/:taskKey/tracker-status",
29184
+ "POST /projects/:projectId/tasks/:taskKey/unarchive",
29185
+ "POST /projects/:projectId/tracker-sync",
29186
+ "POST /runner/reg-token",
29187
+ "POST /sandbox/reg-token",
29188
+ "PUT /api/admin/farms/:farmId/quota",
29189
+ "PUT /api/admin/settings",
29190
+ "PUT /api/notifications/settings",
29191
+ "PUT /projects/:projectId/adr/:file",
29192
+ "PUT /projects/:projectId/prompts/:role",
29193
+ "PUT /projects/:projectId/tasks/:taskKey/branch-name",
29194
+ "PUT /projects/:projectId/tasks/:taskKey/cluster-base",
29195
+ "PUT /projects/:projectId/tasks/:taskKey/commit-summary",
29196
+ "PUT /projects/:projectId/tasks/:taskKey/description",
29197
+ "PUT /projects/:projectId/tasks/:taskKey/solution"
29198
+ ];
29199
+
29075
29200
  // src/http/browser-security.ts
29076
29201
  init_auth();
29077
29202
  import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
@@ -38000,6 +38125,7 @@ function clearPasswordAttempts(key) {
38000
38125
 
38001
38126
  // src/routes/auth.ts
38002
38127
  init_users();
38128
+ init_route_registry();
38003
38129
  async function authRoutes(app, deps) {
38004
38130
  const { db, config, mailer } = deps;
38005
38131
  const loginByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 30 });
@@ -38040,18 +38166,21 @@ async function authRoutes(app, deps) {
38040
38166
  function registerAccountRoutes() {
38041
38167
  app.post(
38042
38168
  "/auth/login",
38043
- {
38044
- schema: {
38045
- body: {
38046
- type: "object",
38047
- required: ["email"],
38048
- properties: {
38049
- email: { type: "string", minLength: 3, maxLength: 254 },
38050
- pwa_nonce: { type: "string", minLength: 1, maxLength: 64 }
38051
- }
38169
+ routeMeta({
38170
+ surface: "bootstrap",
38171
+ operationId: "auth.login",
38172
+ authPolicy: "public",
38173
+ stability: "preview",
38174
+ summary: "Sign in: send a magic link",
38175
+ requestSchema: {
38176
+ type: "object",
38177
+ required: ["email"],
38178
+ properties: {
38179
+ email: { type: "string", minLength: 3, maxLength: 254 },
38180
+ pwa_nonce: { type: "string", minLength: 1, maxLength: 64 }
38052
38181
  }
38053
38182
  }
38054
- },
38183
+ }),
38055
38184
  async (req, reply) => {
38056
38185
  const email = (req.body.email ?? "").trim().toLowerCase();
38057
38186
  if (loginThrottled(email, req.ip)) {
@@ -38069,18 +38198,21 @@ async function authRoutes(app, deps) {
38069
38198
  );
38070
38199
  app.post(
38071
38200
  "/auth/login-start",
38072
- {
38073
- schema: {
38074
- body: {
38075
- type: "object",
38076
- required: ["email"],
38077
- properties: {
38078
- email: { type: "string", minLength: 3, maxLength: 254 },
38079
- pwa_nonce: { type: "string", minLength: 1, maxLength: 64 }
38080
- }
38201
+ routeMeta({
38202
+ surface: "bootstrap",
38203
+ operationId: "auth.loginStart",
38204
+ authPolicy: "public",
38205
+ stability: "preview",
38206
+ summary: "Start a sign-in, without saying whether the account exists",
38207
+ requestSchema: {
38208
+ type: "object",
38209
+ required: ["email"],
38210
+ properties: {
38211
+ email: { type: "string", minLength: 3, maxLength: 254 },
38212
+ pwa_nonce: { type: "string", minLength: 1, maxLength: 64 }
38081
38213
  }
38082
38214
  }
38083
- },
38215
+ }),
38084
38216
  async (req, reply) => {
38085
38217
  const email = (req.body.email ?? "").trim().toLowerCase();
38086
38218
  if (loginThrottled(email, req.ip)) {
@@ -38102,18 +38234,21 @@ async function authRoutes(app, deps) {
38102
38234
  );
38103
38235
  app.post(
38104
38236
  "/auth/login-password",
38105
- {
38106
- schema: {
38107
- body: {
38108
- type: "object",
38109
- required: ["email", "password"],
38110
- properties: {
38111
- email: { type: "string", minLength: 3, maxLength: 254 },
38112
- password: { type: "string", minLength: 1, maxLength: MAX_PASSWORD_LENGTH }
38113
- }
38237
+ routeMeta({
38238
+ surface: "bootstrap",
38239
+ operationId: "auth.loginPassword",
38240
+ authPolicy: "public",
38241
+ stability: "preview",
38242
+ summary: "Sign in with a password",
38243
+ requestSchema: {
38244
+ type: "object",
38245
+ required: ["email", "password"],
38246
+ properties: {
38247
+ email: { type: "string", minLength: 3, maxLength: 254 },
38248
+ password: { type: "string", minLength: 1, maxLength: MAX_PASSWORD_LENGTH }
38114
38249
  }
38115
38250
  }
38116
- },
38251
+ }),
38117
38252
  async (req, reply) => {
38118
38253
  const email = (req.body.email ?? "").trim().toLowerCase();
38119
38254
  const password = req.body.password ?? "";
@@ -38137,15 +38272,18 @@ async function authRoutes(app, deps) {
38137
38272
  );
38138
38273
  app.post(
38139
38274
  "/auth/set-password",
38140
- {
38141
- schema: {
38142
- body: {
38143
- type: "object",
38144
- required: ["password"],
38145
- properties: { password: { type: "string", minLength: 1, maxLength: 512 } }
38146
- }
38275
+ routeMeta({
38276
+ surface: "bootstrap",
38277
+ operationId: "auth.setPassword",
38278
+ authPolicy: "session",
38279
+ stability: "preview",
38280
+ summary: "Set the signed-in account's password",
38281
+ requestSchema: {
38282
+ type: "object",
38283
+ required: ["password"],
38284
+ properties: { password: { type: "string", minLength: 1, maxLength: 512 } }
38147
38285
  }
38148
- },
38286
+ }),
38149
38287
  async (req, reply) => {
38150
38288
  const session = await requireSession(db, req, reply);
38151
38289
  if (!session) return reply;
@@ -38164,7 +38302,14 @@ async function authRoutes(app, deps) {
38164
38302
  return reply.send({ status: "ok" });
38165
38303
  }
38166
38304
  );
38167
- app.get("/auth/verify", async (req, reply) => {
38305
+ app.get("/auth/verify", routeMeta({
38306
+ surface: "bootstrap",
38307
+ operationId: "auth.verify",
38308
+ authPolicy: "public",
38309
+ // A redirect from an email link, not a JSON operation to generate a client for.
38310
+ stability: "internal",
38311
+ summary: "Redeem a magic link"
38312
+ }), async (req, reply) => {
38168
38313
  const wantsHtml = (req.headers.accept ?? "").toLowerCase().includes("text/html");
38169
38314
  const token = (req.query.token ?? "").trim();
38170
38315
  if (!token) {
@@ -38186,7 +38331,14 @@ async function authRoutes(app, deps) {
38186
38331
  });
38187
38332
  }
38188
38333
  function registerSessionRoutes() {
38189
- app.get("/auth/me", async (req, reply) => {
38334
+ app.get("/auth/me", routeMeta({
38335
+ surface: "bootstrap",
38336
+ operationId: "auth.me",
38337
+ // A cookie, or the single-owner's credential on the local profile.
38338
+ authPolicy: "application_actor",
38339
+ stability: "preview",
38340
+ summary: "Who is signed in"
38341
+ }), async (req, reply) => {
38190
38342
  const session = await readSession(db, req);
38191
38343
  if (!session) return reply.status(401).send({ error: "no_session" });
38192
38344
  const user = await findUser(db, session.email);
@@ -38198,7 +38350,13 @@ async function authRoutes(app, deps) {
38198
38350
  singleOwner
38199
38351
  };
38200
38352
  });
38201
- app.post("/auth/logout", async (req, reply) => {
38353
+ app.post("/auth/logout", routeMeta({
38354
+ surface: "bootstrap",
38355
+ operationId: "auth.logout",
38356
+ authPolicy: "public",
38357
+ stability: "preview",
38358
+ summary: "Sign out"
38359
+ }), async (req, reply) => {
38202
38360
  const cookie2 = req.cookies[SESSION_COOKIE_NAME];
38203
38361
  if (cookie2) {
38204
38362
  const unsigned = req.unsignCookie(cookie2);
@@ -38209,7 +38367,13 @@ async function authRoutes(app, deps) {
38209
38367
  reply.clearCookie(SESSION_COOKIE_NAME, { path: "/" });
38210
38368
  return { status: "ok" };
38211
38369
  });
38212
- app.get("/auth/pwa-ready", async (req, reply) => {
38370
+ app.get("/auth/pwa-ready", routeMeta({
38371
+ surface: "bootstrap",
38372
+ operationId: "auth.pwaReady",
38373
+ authPolicy: "public",
38374
+ stability: "preview",
38375
+ summary: "Whether an installed app's sign-in has completed"
38376
+ }), async (req, reply) => {
38213
38377
  const nonce = (req.query.nonce ?? "").trim();
38214
38378
  if (!nonce) return reply.status(400).send({ error: "missing_nonce" });
38215
38379
  const result = await consumePwaNonce(db, nonce);
@@ -38234,6 +38398,7 @@ import { nanoid as nanoid41 } from "nanoid";
38234
38398
  init_settings();
38235
38399
  init_users();
38236
38400
  init_cli_tokens();
38401
+ init_route_registry();
38237
38402
  var REQUEST_TTL_MS = 15 * 6e4;
38238
38403
  var APPROVAL_TTL_MS = 24 * 60 * 6e4;
38239
38404
  async function cliBootstrapRoutes(app, deps) {
@@ -38267,15 +38432,18 @@ async function cliBootstrapRoutes(app, deps) {
38267
38432
  }
38268
38433
  app.post(
38269
38434
  "/cli/bootstrap-request",
38270
- {
38271
- schema: {
38272
- body: {
38273
- type: "object",
38274
- required: ["email"],
38275
- properties: { email: { type: "string", minLength: 3, maxLength: 254 } }
38276
- }
38435
+ routeMeta({
38436
+ surface: "bootstrap",
38437
+ operationId: "bootstrap.request",
38438
+ authPolicy: "public",
38439
+ stability: "preview",
38440
+ summary: "A CLI asks to be connected to an account",
38441
+ requestSchema: {
38442
+ type: "object",
38443
+ required: ["email"],
38444
+ properties: { email: { type: "string", minLength: 3, maxLength: 254 } }
38277
38445
  }
38278
- },
38446
+ }),
38279
38447
  async (req, reply) => {
38280
38448
  const email = (req.body.email ?? "").trim().toLowerCase();
38281
38449
  if (!isValidEmail(email)) {
@@ -38323,6 +38491,14 @@ async function cliBootstrapRoutes(app, deps) {
38323
38491
  );
38324
38492
  app.get(
38325
38493
  "/cli/authorize",
38494
+ routeMeta({
38495
+ surface: "bootstrap",
38496
+ operationId: "bootstrap.authorizePage",
38497
+ authPolicy: "public",
38498
+ // An HTML page, not a JSON operation to generate a client for.
38499
+ stability: "internal",
38500
+ summary: "The page where a person confirms a CLI connection"
38501
+ }),
38326
38502
  async (req, reply) => {
38327
38503
  const requestId = (req.query.request ?? "").trim();
38328
38504
  const row = requestId ? await loadRequest(requestId) : void 0;
@@ -38371,15 +38547,19 @@ async function cliBootstrapRoutes(app, deps) {
38371
38547
  );
38372
38548
  app.post(
38373
38549
  "/cli/authorize/confirm",
38374
- {
38375
- schema: {
38376
- body: {
38377
- type: "object",
38378
- required: ["request"],
38379
- properties: { request: { type: "string", minLength: 1, maxLength: 64 } }
38380
- }
38550
+ routeMeta({
38551
+ surface: "bootstrap",
38552
+ operationId: "bootstrap.authorizeConfirm",
38553
+ // A cookie, or the single-owner's credential on the local profile.
38554
+ authPolicy: "application_actor",
38555
+ stability: "preview",
38556
+ summary: "Confirm a CLI connection as the signed-in person",
38557
+ requestSchema: {
38558
+ type: "object",
38559
+ required: ["request"],
38560
+ properties: { request: { type: "string", minLength: 1, maxLength: 64 } }
38381
38561
  }
38382
- },
38562
+ }),
38383
38563
  async (req, reply) => {
38384
38564
  const session = await readSession(db, req);
38385
38565
  if (!session || !await isAllowedLogin(db, config, session.email)) {
@@ -38397,15 +38577,18 @@ async function cliBootstrapRoutes(app, deps) {
38397
38577
  );
38398
38578
  app.post(
38399
38579
  "/cli/authorize/email",
38400
- {
38401
- schema: {
38402
- body: {
38403
- type: "object",
38404
- required: ["request"],
38405
- properties: { request: { type: "string", minLength: 1, maxLength: 64 } }
38406
- }
38580
+ routeMeta({
38581
+ surface: "bootstrap",
38582
+ operationId: "bootstrap.authorizeEmail",
38583
+ authPolicy: "public",
38584
+ stability: "preview",
38585
+ summary: "Confirm a CLI connection by email instead",
38586
+ requestSchema: {
38587
+ type: "object",
38588
+ required: ["request"],
38589
+ properties: { request: { type: "string", minLength: 1, maxLength: 64 } }
38407
38590
  }
38408
- },
38591
+ }),
38409
38592
  async (req, reply) => {
38410
38593
  const requestId = (req.body.request ?? "").trim();
38411
38594
  const row = await loadRequest(requestId);
@@ -38423,6 +38606,13 @@ async function cliBootstrapRoutes(app, deps) {
38423
38606
  );
38424
38607
  app.get(
38425
38608
  "/cli/bootstrap-confirm",
38609
+ routeMeta({
38610
+ surface: "bootstrap",
38611
+ operationId: "bootstrap.confirm",
38612
+ authPolicy: "public",
38613
+ stability: "internal",
38614
+ summary: "Redeem the CLI connection link from the email"
38615
+ }),
38426
38616
  async (req, reply) => {
38427
38617
  const token = (req.query.token ?? "").trim();
38428
38618
  reply.type("text/html");
@@ -38473,6 +38663,13 @@ async function cliBootstrapRoutes(app, deps) {
38473
38663
  );
38474
38664
  app.get(
38475
38665
  "/cli/bootstrap-poll",
38666
+ routeMeta({
38667
+ surface: "bootstrap",
38668
+ operationId: "bootstrap.poll",
38669
+ authPolicy: "public",
38670
+ stability: "preview",
38671
+ summary: "A CLI polls whether its connection was confirmed"
38672
+ }),
38476
38673
  async (req, reply) => {
38477
38674
  const requestId = (req.query.request ?? "").trim();
38478
38675
  if (!requestId) return reply.status(400).send({ error: "missing_request" });
@@ -38512,13 +38709,20 @@ import { nanoid as nanoid42 } from "nanoid";
38512
38709
  init_auth();
38513
38710
  init_settings();
38514
38711
  init_users();
38712
+ init_route_registry();
38515
38713
  var CHALLENGE_TTL_MS = 10 * 6e4;
38516
38714
  var MIN_FILL_MS = 2e3;
38517
38715
  async function registrationRoutes(app, deps) {
38518
38716
  const { db, config, mailer } = deps;
38519
38717
  const submitByIp = createAbuseThrottle({ windowMs: 60 * 6e4, max: 10 });
38520
38718
  const challengeByIp = createAbuseThrottle({ windowMs: 60 * 6e4, max: 60 });
38521
- app.get("/auth/register-challenge", async (req, reply) => {
38719
+ app.get("/auth/register-challenge", routeMeta({
38720
+ surface: "bootstrap",
38721
+ operationId: "auth.registerChallenge",
38722
+ authPolicy: "public",
38723
+ stability: "preview",
38724
+ summary: "The challenge a registration form answers"
38725
+ }), async (req, reply) => {
38522
38726
  if (!challengeByIp.take(`ip:${req.ip}`)) {
38523
38727
  return reply.status(429).send({ error: "too_many_requests" });
38524
38728
  }
@@ -38532,21 +38736,24 @@ async function registrationRoutes(app, deps) {
38532
38736
  });
38533
38737
  app.post(
38534
38738
  "/auth/register",
38535
- {
38536
- schema: {
38537
- body: {
38538
- type: "object",
38539
- required: ["email", "challengeId"],
38540
- properties: {
38541
- email: { type: "string", minLength: 3, maxLength: 254 },
38542
- name: { type: "string", maxLength: 200 },
38543
- note: { type: "string", maxLength: 1e3 },
38544
- challengeId: { type: "string", minLength: 1, maxLength: 64 },
38545
- website: { type: "string", maxLength: 200 }
38546
- }
38739
+ routeMeta({
38740
+ surface: "bootstrap",
38741
+ operationId: "auth.register",
38742
+ authPolicy: "public",
38743
+ stability: "preview",
38744
+ summary: "Ask for an account",
38745
+ requestSchema: {
38746
+ type: "object",
38747
+ required: ["email", "challengeId"],
38748
+ properties: {
38749
+ email: { type: "string", minLength: 3, maxLength: 254 },
38750
+ name: { type: "string", maxLength: 200 },
38751
+ note: { type: "string", maxLength: 1e3 },
38752
+ challengeId: { type: "string", minLength: 1, maxLength: 64 },
38753
+ website: { type: "string", maxLength: 200 }
38547
38754
  }
38548
38755
  }
38549
- },
38756
+ }),
38550
38757
  async (req, reply) => {
38551
38758
  const email = (req.body.email ?? "").trim().toLowerCase();
38552
38759
  const name = (req.body.name ?? "").trim() || null;
@@ -38594,7 +38801,13 @@ async function registrationRoutes(app, deps) {
38594
38801
  return reply.status(202).send({ status: "pending" });
38595
38802
  }
38596
38803
  );
38597
- app.get("/auth/register-approve", async (req, reply) => {
38804
+ app.get("/auth/register-approve", routeMeta({
38805
+ surface: "bootstrap",
38806
+ operationId: "auth.registerApprove",
38807
+ authPolicy: "public",
38808
+ stability: "internal",
38809
+ summary: "Approve a registration request from its link"
38810
+ }), async (req, reply) => {
38598
38811
  const token = (req.query.token ?? "").trim();
38599
38812
  const row = token ? await db.get(`SELECT id, email, name, note, status, approve_token as approveToken,
38600
38813
  reject_token as rejectToken, created_at as createdAt,
@@ -38614,7 +38827,13 @@ async function registrationRoutes(app, deps) {
38614
38827
  )
38615
38828
  );
38616
38829
  });
38617
- app.get("/auth/register-reject", async (req, reply) => {
38830
+ app.get("/auth/register-reject", routeMeta({
38831
+ surface: "bootstrap",
38832
+ operationId: "auth.registerReject",
38833
+ authPolicy: "public",
38834
+ stability: "internal",
38835
+ summary: "Reject a registration request from its link"
38836
+ }), async (req, reply) => {
38618
38837
  const token = (req.query.token ?? "").trim();
38619
38838
  const row = token ? await db.get(`SELECT id, email, name, status FROM registration_requests WHERE reject_token = ?`, token) : void 0;
38620
38839
  if (!row) {
@@ -38816,6 +39035,7 @@ function safeNext(next) {
38816
39035
  }
38817
39036
 
38818
39037
  // src/routes/install.ts
39038
+ init_route_registry();
38819
39039
  import { fileURLToPath as fileURLToPath2 } from "node:url";
38820
39040
  import { dirname as dirname3, resolve as resolve2 } from "node:path";
38821
39041
  import { existsSync as existsSync6 } from "node:fs";
@@ -38826,7 +39046,14 @@ var SCRIPT_PATH = [
38826
39046
  resolve2(MODULE_DIR2, "../../../deploy/install-sandbox.sh")
38827
39047
  ].find((p) => existsSync6(p)) ?? resolve2(MODULE_DIR2, "../../../deploy/install-sandbox.sh");
38828
39048
  async function installRoutes(app) {
38829
- app.get("/install-sandbox.sh", async (_req, reply) => {
39049
+ app.get("/install-sandbox.sh", routeMeta({
39050
+ surface: "static",
39051
+ operationId: "ops.installSandboxScript",
39052
+ authPolicy: "public",
39053
+ // A shell script, not a JSON operation.
39054
+ stability: "internal",
39055
+ summary: "The SANDBOX install script"
39056
+ }), async (_req, reply) => {
38830
39057
  try {
38831
39058
  const body = await readFile(SCRIPT_PATH, "utf8");
38832
39059
  return reply.type("text/x-shellscript; charset=utf-8").send(body);
@@ -41561,9 +41788,11 @@ async function buildServer(config, db) {
41561
41788
  }
41562
41789
  });
41563
41790
  }
41564
- const routeRegistry = installRouteGuard(app, "observe", {
41565
- warn: (message) => app.log.warn(message)
41566
- });
41791
+ const routeRegistry = installRouteGuard(app, "enforce", { warn: (message) => app.log.warn(message) }, [
41792
+ ...LEGACY_ROUTES,
41793
+ ...STATIC_PLUGIN_ROUTES
41794
+ ]);
41795
+ app.decorate("routeRegistry", routeRegistry);
41567
41796
  const mailer = buildMailer(loadMailerConfig(), app.log);
41568
41797
  app.log.info({ mailer: mailer.mode }, "mailer mode");
41569
41798
  const notifyDeps = {
@@ -41785,10 +42014,9 @@ async function buildServer(config, db) {
41785
42014
  app.log.info(
41786
42015
  {
41787
42016
  classified: routeRegistry.list().length,
41788
- unclassified: routeRegistry.unclassifiedCount(),
41789
- invalid: routeRegistry.invalidCount()
42017
+ legacy: routeRegistry.unclassifiedRoutes().filter((r) => !STATIC_PLUGIN_ROUTES.includes(r)).length
41790
42018
  },
41791
- "route registry: routes still to declare their metadata"
42019
+ "route registry: legacy routes still served for the browser"
41792
42020
  );
41793
42021
  });
41794
42022
  await registerSpaHosts(app, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-partner/codepipe-hub",
3
- "version": "0.14.1-dev.423.ga49d1f61",
3
+ "version": "0.14.1-dev.424.ge3c8cc77",
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",