securewithtrace 0.31.0 → 0.32.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 (2) hide show
  1. package/dist/index.js +1894 -495
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6322,29 +6322,29 @@ var ZodFunction = class _ZodFunction extends ZodType {
6322
6322
  const params = { errorMap: ctx.common.contextualErrorMap };
6323
6323
  const fn = ctx.data;
6324
6324
  if (this._def.returns instanceof ZodPromise) {
6325
- const me = this;
6325
+ const me2 = this;
6326
6326
  return OK(async function(...args) {
6327
6327
  const error48 = new ZodError([]);
6328
- const parsedArgs = await me._def.args.parseAsync(args, params).catch((e2) => {
6328
+ const parsedArgs = await me2._def.args.parseAsync(args, params).catch((e2) => {
6329
6329
  error48.addIssue(makeArgsIssue(args, e2));
6330
6330
  throw error48;
6331
6331
  });
6332
6332
  const result = await Reflect.apply(fn, this, parsedArgs);
6333
- const parsedReturns = await me._def.returns._def.type.parseAsync(result, params).catch((e2) => {
6333
+ const parsedReturns = await me2._def.returns._def.type.parseAsync(result, params).catch((e2) => {
6334
6334
  error48.addIssue(makeReturnsIssue(result, e2));
6335
6335
  throw error48;
6336
6336
  });
6337
6337
  return parsedReturns;
6338
6338
  });
6339
6339
  } else {
6340
- const me = this;
6340
+ const me2 = this;
6341
6341
  return OK(function(...args) {
6342
- const parsedArgs = me._def.args.safeParse(args, params);
6342
+ const parsedArgs = me2._def.args.safeParse(args, params);
6343
6343
  if (!parsedArgs.success) {
6344
6344
  throw new ZodError([makeArgsIssue(args, parsedArgs.error)]);
6345
6345
  }
6346
6346
  const result = Reflect.apply(fn, this, parsedArgs.data);
6347
- const parsedReturns = me._def.returns.safeParse(result, params);
6347
+ const parsedReturns = me2._def.returns.safeParse(result, params);
6348
6348
  if (!parsedReturns.success) {
6349
6349
  throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]);
6350
6350
  }
@@ -10176,7 +10176,7 @@ var $ZodBase64 = /* @__PURE__ */ $constructor("$ZodBase64", (inst, def) => {
10176
10176
  function isValidBase64URL(data) {
10177
10177
  if (!base64url.test(data))
10178
10178
  return false;
10179
- const base643 = data.replace(/[-_]/g, (c50) => c50 === "-" ? "+" : "/");
10179
+ const base643 = data.replace(/[-_]/g, (c49) => c49 === "-" ? "+" : "/");
10180
10180
  const padded = base643.padEnd(Math.ceil(base643.length / 4) * 4, "=");
10181
10181
  return isValidBase64(padded);
10182
10182
  }
@@ -20220,9 +20220,9 @@ var ZodDate2 = /* @__PURE__ */ $constructor("ZodDate", (inst, def) => {
20220
20220
  inst._zod.processJSONSchema = (ctx, json2, params) => dateProcessor(inst, ctx, json2, params);
20221
20221
  inst.min = (value, params) => inst.check(_gte(value, params));
20222
20222
  inst.max = (value, params) => inst.check(_lte(value, params));
20223
- const c50 = inst._zod.bag;
20224
- inst.minDate = c50.minimum ? new Date(c50.minimum) : null;
20225
- inst.maxDate = c50.maximum ? new Date(c50.maximum) : null;
20223
+ const c49 = inst._zod.bag;
20224
+ inst.minDate = c49.minimum ? new Date(c49.minimum) : null;
20225
+ inst.maxDate = c49.maximum ? new Date(c49.maximum) : null;
20226
20226
  });
20227
20227
  function date3(params) {
20228
20228
  return _date(ZodDate2, params);
@@ -21504,7 +21504,8 @@ var PentestEditEntityValues = [
21504
21504
  "PENTEST_TOOL_RUN",
21505
21505
  "PENTEST_ATTESTATION",
21506
21506
  "PENTEST_REPORT",
21507
- "PENTEST_EXPLOIT_ARTIFACT"
21507
+ "PENTEST_EXPLOIT_ARTIFACT",
21508
+ "PENTEST_FINDING_RETEST"
21508
21509
  ];
21509
21510
  var PentestEditEntity = external_exports.enum(PentestEditEntityValues);
21510
21511
  var PentestEditActionValues = [
@@ -21688,10 +21689,11 @@ var AgentSessionIdParamsSchema = AgentSessionsParamsSchema.extend({
21688
21689
  var AgentSessionEventIdParamsSchema = AgentSessionIdParamsSchema.extend({
21689
21690
  eventId: external_exports.uuid()
21690
21691
  });
21692
+ var MAX_AGENT_SESSION_MAX_USD = 1500;
21691
21693
  var CreateAgentSessionCommonSchema = external_exports.object({
21692
21694
  provider: AgentSessionProvider.optional(),
21693
21695
  model: external_exports.string().min(1).max(200).optional(),
21694
- maxUsd: external_exports.number().positive().max(500).optional(),
21696
+ maxUsd: external_exports.number().positive().max(MAX_AGENT_SESSION_MAX_USD).optional(),
21695
21697
  maxWallClockMin: external_exports.number().int().positive().max(24 * 60).optional()
21696
21698
  });
21697
21699
  var CreateAgentSessionRequestSchema = external_exports.discriminatedUnion("type", [
@@ -21701,7 +21703,17 @@ var CreateAgentSessionRequestSchema = external_exports.discriminatedUnion("type"
21701
21703
  }),
21702
21704
  CreateAgentSessionCommonSchema.extend({
21703
21705
  type: external_exports.literal("CODE_REVIEW"),
21704
- focus: external_exports.string().min(1).max(2e3).optional()
21706
+ focus: external_exports.string().min(1).max(2e3).optional(),
21707
+ /**
21708
+ * Which bound application this review reads the codebase through (multi-application
21709
+ * PR 4). REQUIRED once 2+ applications are bound — omitting it there is
21710
+ * `APPLICATION_REQUIRED`, because the silent alternative is N reviews all scoping
21711
+ * their threat model to the first binding and overwriting each other. Defaults to
21712
+ * the sole binding at 1, and is legitimately absent for a bindingless engagement.
21713
+ * Only CODE_REVIEW and PRODUCT_RECON carry it: the other types are engagement-wide
21714
+ * by design, and the discriminated union is what keeps it off them.
21715
+ */
21716
+ applicationId: external_exports.uuid().optional()
21705
21717
  }),
21706
21718
  CreateAgentSessionCommonSchema.extend({
21707
21719
  type: external_exports.literal("VALIDATION"),
@@ -21736,7 +21748,10 @@ var CreateAgentSessionRequestSchema = external_exports.discriminatedUnion("type"
21736
21748
  // / VALIDATION / RECON.
21737
21749
  CreateAgentSessionCommonSchema.extend({
21738
21750
  type: external_exports.literal("PRODUCT_RECON"),
21739
- focus: external_exports.string().min(1).max(2e3).optional()
21751
+ focus: external_exports.string().min(1).max(2e3).optional(),
21752
+ /** Which bound application this recon learns. Same rules as CODE_REVIEW's, except
21753
+ * a bindingless engagement is already rejected at prep (PRODUCT_RECON_NO_APPLICATION). */
21754
+ applicationId: external_exports.uuid().optional()
21740
21755
  }),
21741
21756
  // REPORT (report-generation-session RS3): the report-authoring session. The
21742
21757
  // objective is server-built from the engagement (no repo/creds); `focus` is
@@ -21752,8 +21767,10 @@ var SendAgentSessionMessageRequestSchema = external_exports.object({
21752
21767
  var ResumeAgentSessionRequestSchema = external_exports.object({
21753
21768
  /** Manual failover: relaunch on the other provider (cold cache accepted). */
21754
21769
  provider: AgentSessionProvider.optional(),
21755
- /** Budget raise — required to leave WAITING_ON_STAFF(BUDGET_EXHAUSTED). */
21756
- maxUsd: external_exports.number().positive().max(500).optional()
21770
+ /** Budget raise — required to leave WAITING_ON_STAFF(BUDGET_EXHAUSTED). Same
21771
+ * lifetime ceiling as create: a raise past it is not expressible, which is why
21772
+ * the create cap had to move for the engagement-wide validator. */
21773
+ maxUsd: external_exports.number().positive().max(MAX_AGENT_SESSION_MAX_USD).optional()
21757
21774
  });
21758
21775
  var ListAgentSessionEventsQuerySchema = external_exports.object({
21759
21776
  /** Return events with seq > sinceSeq (poll cursor). */
@@ -21765,6 +21782,12 @@ var AgentSessionSummarySchema = external_exports.object({
21765
21782
  organizationId: external_exports.uuid(),
21766
21783
  pentestId: external_exports.uuid(),
21767
21784
  type: AgentSessionType,
21785
+ /**
21786
+ * The application this session is scoped to, or null for engagement-wide (PR 4).
21787
+ * Id only — `toSummary` is a pure row mapper feeding six call sites, and the staff
21788
+ * console already holds the bound applications it needs to resolve a name.
21789
+ */
21790
+ applicationId: external_exports.uuid().nullable(),
21768
21791
  status: AgentSessionStatus,
21769
21792
  waitingReason: AgentSessionWaitingReason.nullable(),
21770
21793
  failureReason: AgentSessionFailureReason.nullable(),
@@ -21861,11 +21884,30 @@ var OperatorPentestContextSchema = external_exports.object({
21861
21884
  customerPriorityCategories: external_exports.array(external_exports.string()),
21862
21885
  status: PentestStatus,
21863
21886
  currentStage: PentestStage,
21887
+ /**
21888
+ * The lowest-ordinal binding. RETAINED rather than replaced by `applications`:
21889
+ * `apps/agent-session-host` mirrors this interface by hand and raw-casts the JSON
21890
+ * without a schema parse (`api-client.ts:858-872`), so dropping this field would
21891
+ * give a parked or in-flight session `undefined` for its application block with no
21892
+ * parse error and no log. It retires a release after no parked session predates
21893
+ * `applications`.
21894
+ */
21864
21895
  primaryApplication: external_exports.object({
21865
21896
  name: external_exports.string(),
21866
21897
  type: external_exports.string(),
21867
21898
  applicationUrl: external_exports.string().nullable()
21868
21899
  }).nullable(),
21900
+ /**
21901
+ * Every bound application, ordinal-ascending. Optional for the same host
21902
+ * raw-cast reason: an older host image ignores what it does not know about.
21903
+ */
21904
+ applications: external_exports.array(
21905
+ external_exports.object({
21906
+ name: external_exports.string(),
21907
+ type: external_exports.string(),
21908
+ applicationUrl: external_exports.string().nullable()
21909
+ })
21910
+ ).optional(),
21869
21911
  environments: external_exports.array(
21870
21912
  external_exports.object({
21871
21913
  name: external_exports.string(),
@@ -22197,36 +22239,9 @@ function roleDisplay(role, roleLabel) {
22197
22239
  return APPLICATION_REPOSITORY_ROLE_LABELS[role] ?? null;
22198
22240
  }
22199
22241
 
22200
- // ../../packages/core/src/ai-tooling/profile.ts
22201
- var AI_TOOL_DETECTION = ["claude-code", "codex", "both", "neither"];
22202
- var AI_TOOL_OVERRIDE = ["claude-code", "codex"];
22203
- var Sha40 = external_exports.string().regex(/^[a-f0-9]{40}$/, "expected a 40-char hex git SHA");
22204
- var AiToolingProfileSchema = external_exports.object({
22205
- schemaVersion: external_exports.literal(1),
22206
- detectedTool: external_exports.enum(AI_TOOL_DETECTION),
22207
- overrideTool: external_exports.enum(AI_TOOL_OVERRIDE).nullable(),
22208
- /** null = no detection has run yet (override-only seed); refresh on next scan. */
22209
- detectedAt: external_exports.iso.datetime().nullable(),
22210
- /** Scan commit-sha the detection ran against. null when detectedAt is null. */
22211
- detectedCommitSha: Sha40.nullable(),
22212
- evidence: external_exports.object({
22213
- claudeMdSha: Sha40.nullable(),
22214
- agentsMdSha: Sha40.nullable(),
22215
- claudeMdMtime: external_exports.iso.datetime().nullable(),
22216
- agentsMdMtime: external_exports.iso.datetime().nullable()
22217
- }).strict()
22218
- }).strict();
22219
-
22220
22242
  // ../../packages/api-contracts/src/schemas/api/v1/repository.api.ts
22221
22243
  var PlatformValues = ["GITHUB", "GITLAB", "BITBUCKET"];
22222
22244
  var RepositoryStatusValues = ["CONNECTED", "DISCONNECTED"];
22223
- var SetAiToolingProfileOverrideRequestSchema = external_exports.object({
22224
- /** null clears the override and reverts to detection-driven resolution. */
22225
- overrideTool: external_exports.enum(AI_TOOL_OVERRIDE).nullable()
22226
- }).strict();
22227
- var SetAiToolingProfileOverrideResponseSchema = external_exports.object({
22228
- aiToolingProfile: AiToolingProfileSchema.nullable()
22229
- }).strict();
22230
22245
  var RepositoryIdParamsSchema = OrgIdParamsSchema.extend({
22231
22246
  repositoryId: external_exports.uuid()
22232
22247
  });
@@ -22251,13 +22266,6 @@ var RepositoryResponseSchema = external_exports.object({
22251
22266
  githubDefaultBranch: external_exports.string(),
22252
22267
  scanningBranch: external_exports.string().nullable(),
22253
22268
  primaryLanguage: external_exports.string().nullable().optional(),
22254
- /**
22255
- * Null until the first post-scan detection writes a row, OR when the raw
22256
- * JSONB fails strict-schema parsing (mapper falls back to null so the
22257
- * response stays valid). PATCH override writes always preserve the live
22258
- * `overrideTool` via `parseOverrideTool`.
22259
- */
22260
- aiToolingProfile: AiToolingProfileSchema.nullable(),
22261
22269
  createdAt: external_exports.date(),
22262
22270
  updatedAt: external_exports.date()
22263
22271
  });
@@ -22345,7 +22353,8 @@ var ConfirmScanningBranchDefaultsResponseSchema = external_exports.object({
22345
22353
  });
22346
22354
 
22347
22355
  // ../../packages/api-contracts/src/schemas/api/v1/application.api.ts
22348
- var ApplicationTypeValues = ["WEB", "CLI", "MOBILE", "API_SERVICE"];
22356
+ var ApplicationTypeValues = ["WEB", "CLI", "MOBILE", "DESKTOP", "API_SERVICE"];
22357
+ var APPLICATION_NOTES_MAX_CHARS = 2e3;
22349
22358
  var ApplicationRepositoryRoleValues = [
22350
22359
  "FRONTEND",
22351
22360
  "BACKEND_API",
@@ -22356,12 +22365,27 @@ var ApplicationRepositoryRoleValues = [
22356
22365
  "MOBILE_CLIENT",
22357
22366
  "OTHER"
22358
22367
  ];
22368
+ var ScopeDomainSourceValues = ["DERIVED", "MANUAL"];
22359
22369
  var ApplicationIdParamsSchema = OrgIdParamsSchema.extend({
22360
22370
  applicationId: external_exports.uuid()
22361
22371
  });
22362
22372
  var ApplicationRepositoryIdParamsSchema = ApplicationIdParamsSchema.extend({
22363
22373
  applicationRepositoryId: external_exports.uuid()
22364
22374
  });
22375
+ var AuthorizedDomainIdParamsSchema = ApplicationIdParamsSchema.extend({
22376
+ applicationAuthorizedDomainId: external_exports.uuid()
22377
+ });
22378
+ var AuthorizedDomainResponseSchema = external_exports.object({
22379
+ applicationAuthorizedDomainId: external_exports.uuid(),
22380
+ applicationId: external_exports.uuid(),
22381
+ organizationId: external_exports.uuid(),
22382
+ domain: external_exports.string(),
22383
+ source: external_exports.enum(ScopeDomainSourceValues),
22384
+ createdAt: external_exports.date()
22385
+ });
22386
+ var CreateAuthorizedDomainRequestSchema = external_exports.object({
22387
+ domain: external_exports.string().trim().min(1).max(253)
22388
+ }).strict();
22365
22389
  var ApplicationRepositorySummarySchema = external_exports.object({
22366
22390
  applicationRepositoryId: external_exports.uuid().nullable(),
22367
22391
  applicationId: external_exports.uuid(),
@@ -22393,9 +22417,20 @@ var ApplicationResponseSchema = external_exports.object({
22393
22417
  // reject application rows.
22394
22418
  type: external_exports.enum(ApplicationTypeValues).default("WEB"),
22395
22419
  applicationUrl: external_exports.string().nullable(),
22420
+ // Customer-authored free-text context, any type ("Advanced → Notes").
22421
+ notes: external_exports.string().nullable(),
22396
22422
  helpCenterUrl: external_exports.string().nullable(),
22397
22423
  documentationUrl: external_exports.string().nullable(),
22398
22424
  repositories: external_exports.array(ApplicationRepositorySummarySchema),
22425
+ // Registrable domains RECON is authorized to discover subdomains under. The
22426
+ // Environments-tab panel hydrates from this payload (no client GET). Defaulted
22427
+ // (like `type`) so a new client reading a pre-this-release API during a rolling
22428
+ // deploy doesn't reject application rows that predate the field.
22429
+ authorizedDomains: external_exports.array(AuthorizedDomainResponseSchema).default([]),
22430
+ // Signed S3 URL for the Overview screenshot. Defaulted null so a new client
22431
+ // reading a pre-this-release API during a rolling deploy doesn't reject rows
22432
+ // that omit the field. The raw S3 key is never on the wire.
22433
+ screenshotUrl: external_exports.url().nullable().default(null),
22399
22434
  createdAt: external_exports.date(),
22400
22435
  updatedAt: external_exports.date()
22401
22436
  });
@@ -22404,6 +22439,7 @@ var CreateApplicationRequestSchema = external_exports.object({
22404
22439
  name: external_exports.string().min(1).max(200),
22405
22440
  type: external_exports.enum(ApplicationTypeValues).default("WEB"),
22406
22441
  applicationUrl: external_exports.url().max(2048).optional(),
22442
+ notes: external_exports.string().max(APPLICATION_NOTES_MAX_CHARS).optional(),
22407
22443
  repositoryId: external_exports.uuid().optional(),
22408
22444
  repositoryPath: external_exports.string().max(500).optional(),
22409
22445
  helpCenterUrl: external_exports.url().max(2048).optional(),
@@ -22431,6 +22467,7 @@ var UpdateApplicationRepositoryLinkRequestSchema = external_exports.object({
22431
22467
  var UpdateApplicationRequestSchema = external_exports.object({
22432
22468
  name: external_exports.string().min(1).max(200).optional(),
22433
22469
  applicationUrl: external_exports.url().max(2048).optional(),
22470
+ notes: external_exports.string().max(APPLICATION_NOTES_MAX_CHARS).nullable().optional(),
22434
22471
  repositoryId: external_exports.uuid().nullable().optional(),
22435
22472
  repositoryPath: external_exports.string().max(500).nullable().optional(),
22436
22473
  helpCenterUrl: external_exports.url().max(2048).nullable().optional(),
@@ -22565,6 +22602,35 @@ var applicationContract = c4.router({
22565
22602
  409: ErrorResponseSchema,
22566
22603
  500: ErrorResponseSchema
22567
22604
  }
22605
+ },
22606
+ addAuthorizedDomain: {
22607
+ method: "POST",
22608
+ path: "/organizations/:organizationId/applications/:applicationId/authorized-domains",
22609
+ pathParams: ApplicationIdParamsSchema,
22610
+ body: CreateAuthorizedDomainRequestSchema,
22611
+ responses: {
22612
+ 201: AuthorizedDomainResponseSchema,
22613
+ // 400 = INVALID_DOMAIN (not a registrable domain / IP / bare suffix);
22614
+ // 409 = DOMAIN_ALREADY_AUTHORIZED.
22615
+ 400: ErrorResponseSchema,
22616
+ 401: ErrorResponseSchema,
22617
+ 403: ErrorResponseSchema,
22618
+ 404: ErrorResponseSchema,
22619
+ 409: ErrorResponseSchema,
22620
+ 500: ErrorResponseSchema
22621
+ }
22622
+ },
22623
+ deleteAuthorizedDomain: {
22624
+ method: "DELETE",
22625
+ path: "/organizations/:organizationId/applications/:applicationId/authorized-domains/:applicationAuthorizedDomainId",
22626
+ pathParams: AuthorizedDomainIdParamsSchema,
22627
+ responses: {
22628
+ 204: DeleteResponseSchema,
22629
+ 401: ErrorResponseSchema,
22630
+ 403: ErrorResponseSchema,
22631
+ 404: ErrorResponseSchema,
22632
+ 500: ErrorResponseSchema
22633
+ }
22568
22634
  }
22569
22635
  });
22570
22636
 
@@ -22794,7 +22860,7 @@ var chatSessionContract = c6.router({
22794
22860
  });
22795
22861
 
22796
22862
  // ../../packages/api-contracts/src/schemas/api/v1/cloud-integration.api.ts
22797
- var CloudProviderValues = ["AWS", "GCP", "AZURE", "VERCEL"];
22863
+ var CloudProviderValues = ["AWS", "GCP", "AZURE", "VERCEL", "CONVEX"];
22798
22864
  var CloudIntegrationStatusValues = [
22799
22865
  "PENDING",
22800
22866
  "ACTIVE",
@@ -23304,6 +23370,107 @@ var DisconnectGcpCloudIntegrationResponseSchema = external_exports.object({
23304
23370
  var CancelPendingGcpCloudIntegrationResponseSchema = external_exports.object({
23305
23371
  deleted: external_exports.literal(true)
23306
23372
  }).strict();
23373
+ var ConvexAccountIdParamsSchema = CloudIntegrationIdParamsSchema.extend({
23374
+ cloudProviderAccountId: external_exports.uuid()
23375
+ });
23376
+ var ConvexDeploymentIdParamsSchema = CloudIntegrationIdParamsSchema.extend({
23377
+ convexLinkedDeploymentId: external_exports.uuid()
23378
+ });
23379
+ var CreateConvexInstallIntentRequestSchema = external_exports.object({
23380
+ displayName: external_exports.string().trim().min(1).max(120).optional()
23381
+ }).strict();
23382
+ var FinalizeConvexCallbackRequestSchema = external_exports.object({
23383
+ code: external_exports.string().trim().min(1).max(4096),
23384
+ state: external_exports.string().trim().min(32).max(512)
23385
+ }).strict();
23386
+ var ConvexCloudIntegrationSummarySchema = external_exports.object({
23387
+ cloudIntegrationId: external_exports.uuid(),
23388
+ organizationId: external_exports.uuid(),
23389
+ provider: external_exports.literal("CONVEX"),
23390
+ status: CloudIntegrationStatusSchema,
23391
+ displayName: external_exports.string(),
23392
+ templateVersion: external_exports.string(),
23393
+ verifiedAt: external_exports.date().nullable(),
23394
+ disconnectedAt: external_exports.date().nullable(),
23395
+ disconnectedById: external_exports.uuid().nullable(),
23396
+ lastErrorCode: external_exports.string().nullable(),
23397
+ lastErrorMessage: external_exports.string().nullable(),
23398
+ createdAt: external_exports.date(),
23399
+ updatedAt: external_exports.date()
23400
+ }).strict();
23401
+ var ConvexLinkedDeploymentSummarySchema = external_exports.object({
23402
+ convexLinkedDeploymentId: external_exports.uuid(),
23403
+ organizationId: external_exports.uuid(),
23404
+ cloudIntegrationId: external_exports.uuid(),
23405
+ cloudProviderAccountId: external_exports.uuid(),
23406
+ cloudProviderConnectionId: external_exports.uuid(),
23407
+ convexProjectId: external_exports.number().int(),
23408
+ deploymentName: external_exports.string(),
23409
+ deploymentType: external_exports.string(),
23410
+ deploymentRegion: external_exports.string().nullable(),
23411
+ convexCloudUrl: external_exports.string(),
23412
+ convexSiteUrl: external_exports.string().nullable(),
23413
+ deploymentExpiresAt: external_exports.date().nullable(),
23414
+ /**
23415
+ * Whether a usable credential envelope is stored. NOT a scope claim: the key is
23416
+ * full-access, because Convex cannot mint a scoped one for an OAuth app. Read-only
23417
+ * is enforced by the broker that holds the key, never by the credential.
23418
+ */
23419
+ hasStoredKey: external_exports.boolean(),
23420
+ status: CloudIntegrationStatusSchema,
23421
+ lastVerifiedAt: external_exports.date().nullable(),
23422
+ lastAuthErrorCode: external_exports.string().nullable(),
23423
+ createdAt: external_exports.date(),
23424
+ updatedAt: external_exports.date()
23425
+ }).strict();
23426
+ var ConvexCloudIntegrationListItemSchema = external_exports.object({
23427
+ integration: ConvexCloudIntegrationSummarySchema,
23428
+ deployments: external_exports.array(ConvexLinkedDeploymentSummarySchema)
23429
+ }).strict();
23430
+ var ListConvexCloudIntegrationsResponseSchema = external_exports.object({
23431
+ integrations: external_exports.array(ConvexCloudIntegrationListItemSchema)
23432
+ }).strict();
23433
+ var CreateConvexInstallIntentResponseSchema = external_exports.object({
23434
+ integration: ConvexCloudIntegrationSummarySchema,
23435
+ installUrl: external_exports.url()
23436
+ }).strict();
23437
+ var FinalizeConvexCallbackResponseSchema = external_exports.object({
23438
+ integration: ConvexCloudIntegrationSummarySchema,
23439
+ deployments: external_exports.array(ConvexLinkedDeploymentSummarySchema),
23440
+ /** Deployments beyond the CONVEX_MAX_DEPLOYMENTS cap that were not connected. */
23441
+ droppedDeploymentCount: external_exports.number().int().nonnegative()
23442
+ }).strict();
23443
+ var DisconnectConvexCloudIntegrationResponseSchema = external_exports.object({
23444
+ integration: ConvexCloudIntegrationSummarySchema,
23445
+ deploymentCount: external_exports.number().int().nonnegative(),
23446
+ clearedKeyCount: external_exports.number().int().nonnegative(),
23447
+ /**
23448
+ * Trace cannot revoke a Convex deploy key: there is no deployment-plane endpoint,
23449
+ * the key is not returned by list_deploy_keys, and delete_deploy_key needs an id
23450
+ * the mint never returns. Removing the Trace OAuth app is the only lever the
23451
+ * customer has. A closed union, so PR 2 owns the wording.
23452
+ */
23453
+ revocationHint: external_exports.literal("REVOKE_OAUTH_APP"),
23454
+ convexApplicationsUrl: external_exports.url().nullable()
23455
+ }).strict();
23456
+ var DisconnectConvexProjectResponseSchema = external_exports.object({
23457
+ integration: ConvexCloudIntegrationSummarySchema,
23458
+ convexProjectId: external_exports.number().int(),
23459
+ deploymentCount: external_exports.number().int().nonnegative(),
23460
+ clearedKeyCount: external_exports.number().int().nonnegative(),
23461
+ revocationHint: external_exports.literal("REVOKE_OAUTH_APP"),
23462
+ convexApplicationsUrl: external_exports.url().nullable()
23463
+ }).strict();
23464
+ var DisconnectConvexDeploymentResponseSchema = external_exports.object({
23465
+ integration: ConvexCloudIntegrationSummarySchema,
23466
+ deployment: ConvexLinkedDeploymentSummarySchema,
23467
+ revocationHint: external_exports.literal("REVOKE_OAUTH_APP"),
23468
+ convexApplicationsUrl: external_exports.url().nullable()
23469
+ }).strict();
23470
+ var ReverifyConvexDeploymentResponseSchema = external_exports.object({
23471
+ integration: ConvexCloudIntegrationSummarySchema,
23472
+ deployment: ConvexLinkedDeploymentSummarySchema
23473
+ }).strict();
23307
23474
 
23308
23475
  // ../../packages/api-contracts/src/contracts/v1/cloud-integration.ts
23309
23476
  var c7 = initContract();
@@ -23604,6 +23771,103 @@ var cloudIntegrationContract = c7.router({
23604
23771
  503: ErrorResponseSchema,
23605
23772
  500: ErrorResponseSchema
23606
23773
  }
23774
+ },
23775
+ listConvexIntegrations: {
23776
+ method: "GET",
23777
+ path: "/organizations/:organizationId/cloud-integrations/convex",
23778
+ pathParams: OrgIdParamsSchema,
23779
+ responses: {
23780
+ 200: ListConvexCloudIntegrationsResponseSchema,
23781
+ 401: ErrorResponseSchema,
23782
+ 403: ErrorResponseSchema,
23783
+ 404: ErrorResponseSchema,
23784
+ 500: ErrorResponseSchema
23785
+ }
23786
+ },
23787
+ createConvexInstallIntent: {
23788
+ method: "POST",
23789
+ path: "/organizations/:organizationId/cloud-integrations/convex/install-intents",
23790
+ pathParams: OrgIdParamsSchema,
23791
+ body: CreateConvexInstallIntentRequestSchema,
23792
+ responses: {
23793
+ 201: CreateConvexInstallIntentResponseSchema,
23794
+ 400: ErrorResponseSchema,
23795
+ 401: ErrorResponseSchema,
23796
+ 403: ErrorResponseSchema,
23797
+ 404: ErrorResponseSchema,
23798
+ 409: ErrorResponseSchema,
23799
+ 500: ErrorResponseSchema
23800
+ }
23801
+ },
23802
+ finalizeConvexCallback: {
23803
+ method: "POST",
23804
+ path: "/organizations/:organizationId/cloud-integrations/convex/callback",
23805
+ pathParams: OrgIdParamsSchema,
23806
+ body: FinalizeConvexCallbackRequestSchema,
23807
+ responses: {
23808
+ 200: FinalizeConvexCallbackResponseSchema,
23809
+ 400: ErrorResponseSchema,
23810
+ 401: ErrorResponseSchema,
23811
+ 403: ErrorResponseSchema,
23812
+ 404: ErrorResponseSchema,
23813
+ 409: ErrorResponseSchema,
23814
+ 503: ErrorResponseSchema,
23815
+ 500: ErrorResponseSchema
23816
+ }
23817
+ },
23818
+ disconnectConvexIntegration: {
23819
+ method: "DELETE",
23820
+ path: "/organizations/:organizationId/cloud-integrations/:cloudIntegrationId/convex",
23821
+ pathParams: CloudIntegrationIdParamsSchema,
23822
+ responses: {
23823
+ 200: DisconnectConvexCloudIntegrationResponseSchema,
23824
+ 401: ErrorResponseSchema,
23825
+ 403: ErrorResponseSchema,
23826
+ 404: ErrorResponseSchema,
23827
+ 500: ErrorResponseSchema
23828
+ }
23829
+ },
23830
+ disconnectConvexProject: {
23831
+ method: "DELETE",
23832
+ path: "/organizations/:organizationId/cloud-integrations/:cloudIntegrationId/convex/accounts/:cloudProviderAccountId",
23833
+ pathParams: ConvexAccountIdParamsSchema,
23834
+ responses: {
23835
+ 200: DisconnectConvexProjectResponseSchema,
23836
+ 400: ErrorResponseSchema,
23837
+ 401: ErrorResponseSchema,
23838
+ 403: ErrorResponseSchema,
23839
+ 404: ErrorResponseSchema,
23840
+ 500: ErrorResponseSchema
23841
+ }
23842
+ },
23843
+ disconnectConvexDeployment: {
23844
+ method: "DELETE",
23845
+ path: "/organizations/:organizationId/cloud-integrations/:cloudIntegrationId/convex/deployments/:convexLinkedDeploymentId",
23846
+ pathParams: ConvexDeploymentIdParamsSchema,
23847
+ responses: {
23848
+ 200: DisconnectConvexDeploymentResponseSchema,
23849
+ 400: ErrorResponseSchema,
23850
+ 401: ErrorResponseSchema,
23851
+ 403: ErrorResponseSchema,
23852
+ 404: ErrorResponseSchema,
23853
+ 500: ErrorResponseSchema
23854
+ }
23855
+ },
23856
+ reverifyConvexDeployment: {
23857
+ method: "POST",
23858
+ path: "/organizations/:organizationId/cloud-integrations/:cloudIntegrationId/convex/deployments/:convexLinkedDeploymentId/reverify",
23859
+ pathParams: ConvexDeploymentIdParamsSchema,
23860
+ body: c7.noBody(),
23861
+ responses: {
23862
+ 200: ReverifyConvexDeploymentResponseSchema,
23863
+ 400: ErrorResponseSchema,
23864
+ 401: ErrorResponseSchema,
23865
+ 403: ErrorResponseSchema,
23866
+ 404: ErrorResponseSchema,
23867
+ 409: ErrorResponseSchema,
23868
+ 503: ErrorResponseSchema,
23869
+ 500: ErrorResponseSchema
23870
+ }
23607
23871
  }
23608
23872
  });
23609
23873
 
@@ -23714,13 +23978,13 @@ var StorageStateSchema = external_exports.object({
23714
23978
  } catch {
23715
23979
  return;
23716
23980
  }
23717
- s.cookies.forEach((c50, i) => {
23718
- const d2 = c50.domain.replace(/^\./, "").toLowerCase();
23981
+ s.cookies.forEach((c49, i) => {
23982
+ const d2 = c49.domain.replace(/^\./, "").toLowerCase();
23719
23983
  if (d2 !== host && !host.endsWith(`.${d2}`)) {
23720
23984
  ctx.addIssue({
23721
23985
  code: "custom",
23722
23986
  path: ["cookies", i, "domain"],
23723
- message: `cookie domain "${c50.domain}" is not the capture origin host "${host}" or a parent domain of it`
23987
+ message: `cookie domain "${c49.domain}" is not the capture origin host "${host}" or a parent domain of it`
23724
23988
  });
23725
23989
  }
23726
23990
  });
@@ -23751,10 +24015,42 @@ var CredentialIdParamsSchema = OrgIdParamsSchema.extend({
23751
24015
  var EnvironmentCredentialsParamsSchema = OrgIdParamsSchema.extend({
23752
24016
  environmentId: external_exports.uuid()
23753
24017
  });
24018
+ var OPAQUE_SECRET_SHAPE = { secretValue: external_exports.string().min(1).max(8192) };
24019
+ var SessionStateSecretValueSchema = external_exports.string().min(1).refine((s) => Buffer.byteLength(s, "utf8") <= 262144, "session state must be \u2264256KB").superRefine((s, ctx) => {
24020
+ const parsed = StorageStateSchema.safeParse(parseJsonOrUndefined(s));
24021
+ if (parsed.success) return;
24022
+ if (parsed.error.issues.length === 0) {
24023
+ ctx.addIssue({
24024
+ code: "custom",
24025
+ message: "secretValue is not a valid StorageState capture"
24026
+ });
24027
+ return;
24028
+ }
24029
+ for (const issue2 of parsed.error.issues) {
24030
+ ctx.addIssue({ code: "custom", message: issue2.message, path: issue2.path });
24031
+ }
24032
+ });
24033
+ var SECRET_SHAPES = {
24034
+ BEARER_TOKEN: OPAQUE_SECRET_SHAPE,
24035
+ JWT_TOKEN: OPAQUE_SECRET_SHAPE,
24036
+ API_KEY: OPAQUE_SECRET_SHAPE,
24037
+ PASSPHRASE: OPAQUE_SECRET_SHAPE,
24038
+ COOKIE: { secretValue: external_exports.string().min(1).max(16384) },
24039
+ // secretValue IS the test-account email (login id + inbox ref). Validated as
24040
+ // an address so a typo is rejected at create/rotate time rather than failing
24041
+ // later when the agent uses it as a login/inbox. 320 = RFC 5321 max length.
24042
+ MAGIC_AUTH: { secretValue: external_exports.email().max(320) },
24043
+ USERNAME_PASSWORD: {
24044
+ username: external_exports.string().min(1).max(500),
24045
+ password: external_exports.string().min(1).max(2048)
24046
+ },
24047
+ SESSION_STATE: { secretValue: SessionStateSecretValueSchema }
24048
+ };
24049
+ var CreateLabelSchema = external_exports.string().max(200).optional();
23754
24050
  var CreateBearerSchema = external_exports.object({
23755
24051
  credentialType: external_exports.literal("BEARER_TOKEN"),
23756
- label: external_exports.string().min(1).max(200),
23757
- secretValue: external_exports.string().min(1).max(8192),
24052
+ label: CreateLabelSchema,
24053
+ ...SECRET_SHAPES.BEARER_TOKEN,
23758
24054
  authConfig: external_exports.object({
23759
24055
  type: external_exports.literal("BEARER_TOKEN"),
23760
24056
  headerName: external_exports.string().min(1).max(200).optional(),
@@ -23763,8 +24059,8 @@ var CreateBearerSchema = external_exports.object({
23763
24059
  }).strict();
23764
24060
  var CreateJwtSchema = external_exports.object({
23765
24061
  credentialType: external_exports.literal("JWT_TOKEN"),
23766
- label: external_exports.string().min(1).max(200),
23767
- secretValue: external_exports.string().min(1).max(8192),
24062
+ label: CreateLabelSchema,
24063
+ ...SECRET_SHAPES.JWT_TOKEN,
23768
24064
  authConfig: external_exports.object({
23769
24065
  type: external_exports.literal("JWT_TOKEN"),
23770
24066
  headerName: external_exports.string().min(1).max(200).optional(),
@@ -23773,8 +24069,8 @@ var CreateJwtSchema = external_exports.object({
23773
24069
  }).strict();
23774
24070
  var CreateApiKeySchema = external_exports.object({
23775
24071
  credentialType: external_exports.literal("API_KEY"),
23776
- label: external_exports.string().min(1).max(200),
23777
- secretValue: external_exports.string().min(1).max(8192),
24072
+ label: CreateLabelSchema,
24073
+ ...SECRET_SHAPES.API_KEY,
23778
24074
  authConfig: external_exports.object({
23779
24075
  type: external_exports.literal("API_KEY"),
23780
24076
  placement: external_exports.enum(["header", "query"]),
@@ -23783,16 +24079,15 @@ var CreateApiKeySchema = external_exports.object({
23783
24079
  }).strict();
23784
24080
  var CreatePassphraseSchema = external_exports.object({
23785
24081
  credentialType: external_exports.literal("PASSPHRASE"),
23786
- label: external_exports.string().min(1).max(200),
23787
- secretValue: external_exports.string().min(1).max(8192),
24082
+ label: CreateLabelSchema,
24083
+ ...SECRET_SHAPES.PASSPHRASE,
23788
24084
  // Passphrases are non-HTTP context — authConfig is empty by design.
23789
24085
  authConfig: external_exports.object({ type: external_exports.literal("PASSPHRASE") }).strict().optional()
23790
24086
  }).strict();
23791
24087
  var CreateUsernamePasswordSchema = external_exports.object({
23792
24088
  credentialType: external_exports.literal("USERNAME_PASSWORD"),
23793
- label: external_exports.string().min(1).max(200),
23794
- username: external_exports.string().min(1).max(500),
23795
- password: external_exports.string().min(1).max(2048),
24089
+ label: CreateLabelSchema,
24090
+ ...SECRET_SHAPES.USERNAME_PASSWORD,
23796
24091
  authConfig: external_exports.object({
23797
24092
  type: external_exports.literal("USERNAME_PASSWORD"),
23798
24093
  method: external_exports.enum(["form", "basic", "json"]),
@@ -23806,8 +24101,8 @@ var CreateUsernamePasswordSchema = external_exports.object({
23806
24101
  }).strict();
23807
24102
  var CreateCookieSchema = external_exports.object({
23808
24103
  credentialType: external_exports.literal("COOKIE"),
23809
- label: external_exports.string().min(1).max(200),
23810
- secretValue: external_exports.string().min(1).max(16384),
24104
+ label: CreateLabelSchema,
24105
+ ...SECRET_SHAPES.COOKIE,
23811
24106
  authConfig: external_exports.object({
23812
24107
  type: external_exports.literal("COOKIE"),
23813
24108
  cookieName: external_exports.string().min(1).max(200),
@@ -23817,11 +24112,8 @@ var CreateCookieSchema = external_exports.object({
23817
24112
  }).strict();
23818
24113
  var CreateMagicAuthSchema = external_exports.object({
23819
24114
  credentialType: external_exports.literal("MAGIC_AUTH"),
23820
- label: external_exports.string().min(1).max(200),
23821
- // secretValue IS the test-account email (login id + inbox ref). Validate as
23822
- // an address so a typo is rejected at create time rather than failing later
23823
- // when the agent uses it as a login/inbox. 320 = RFC 5321 max length.
23824
- secretValue: external_exports.email().max(320),
24115
+ label: CreateLabelSchema,
24116
+ ...SECRET_SHAPES.MAGIC_AUTH,
23825
24117
  authConfig: external_exports.object({
23826
24118
  type: external_exports.literal("MAGIC_AUTH"),
23827
24119
  /** @deprecated inert — retained for back-compat only. */
@@ -23830,21 +24122,8 @@ var CreateMagicAuthSchema = external_exports.object({
23830
24122
  }).strict();
23831
24123
  var CreateSessionStateSchema = external_exports.object({
23832
24124
  credentialType: external_exports.literal("SESSION_STATE"),
23833
- label: external_exports.string().min(1).max(200),
23834
- secretValue: external_exports.string().min(1).refine((s) => Buffer.byteLength(s, "utf8") <= 262144, "session state must be \u2264256KB").superRefine((s, ctx) => {
23835
- const parsed = StorageStateSchema.safeParse(parseJsonOrUndefined(s));
23836
- if (parsed.success) return;
23837
- if (parsed.error.issues.length === 0) {
23838
- ctx.addIssue({
23839
- code: "custom",
23840
- message: "secretValue is not a valid StorageState capture"
23841
- });
23842
- return;
23843
- }
23844
- for (const issue2 of parsed.error.issues) {
23845
- ctx.addIssue({ code: "custom", message: issue2.message, path: issue2.path });
23846
- }
23847
- })
24125
+ label: CreateLabelSchema,
24126
+ ...SECRET_SHAPES.SESSION_STATE
23848
24127
  }).strict();
23849
24128
  var CreateCredentialRequestSchema = external_exports.discriminatedUnion("credentialType", [
23850
24129
  CreateBearerSchema,
@@ -23864,7 +24143,15 @@ var CredentialResponseSchema = external_exports.object({
23864
24143
  credentialType: CredentialType,
23865
24144
  authConfig: CredentialAuthConfigSchema.nullable(),
23866
24145
  createdAt: external_exports.date(),
23867
- updatedAt: external_exports.date()
24146
+ updatedAt: external_exports.date(),
24147
+ /**
24148
+ * When the encrypted secret was last REPLACED. Null for a credential never
24149
+ * rotated (including every one created before rotation existed). Distinct
24150
+ * from `updatedAt`, which also moves on label / environment / authConfig
24151
+ * edits — so only this field can evidence a secret change. Not secret
24152
+ * material: a timestamp, safe on the read path.
24153
+ */
24154
+ secretRotatedAt: external_exports.date().nullable()
23868
24155
  }).strict();
23869
24156
  var ListCredentialsResponseSchema = external_exports.array(CredentialResponseSchema);
23870
24157
  var UpdateCredentialRequestSchema = external_exports.object({
@@ -23872,6 +24159,37 @@ var UpdateCredentialRequestSchema = external_exports.object({
23872
24159
  environmentId: external_exports.uuid().optional(),
23873
24160
  authConfig: CredentialAuthConfigSchema.nullable().optional()
23874
24161
  }).strict();
24162
+ var RotateCredentialSecretRequestSchema = external_exports.discriminatedUnion("credentialType", [
24163
+ external_exports.object({ credentialType: external_exports.literal("BEARER_TOKEN"), ...SECRET_SHAPES.BEARER_TOKEN }).strict(),
24164
+ external_exports.object({ credentialType: external_exports.literal("JWT_TOKEN"), ...SECRET_SHAPES.JWT_TOKEN }).strict(),
24165
+ external_exports.object({ credentialType: external_exports.literal("API_KEY"), ...SECRET_SHAPES.API_KEY }).strict(),
24166
+ external_exports.object({ credentialType: external_exports.literal("PASSPHRASE"), ...SECRET_SHAPES.PASSPHRASE }).strict(),
24167
+ external_exports.object({ credentialType: external_exports.literal("COOKIE"), ...SECRET_SHAPES.COOKIE }).strict(),
24168
+ external_exports.object({ credentialType: external_exports.literal("MAGIC_AUTH"), ...SECRET_SHAPES.MAGIC_AUTH }).strict(),
24169
+ external_exports.object({
24170
+ credentialType: external_exports.literal("USERNAME_PASSWORD"),
24171
+ ...SECRET_SHAPES.USERNAME_PASSWORD
24172
+ }).strict(),
24173
+ external_exports.object({ credentialType: external_exports.literal("SESSION_STATE"), ...SECRET_SHAPES.SESSION_STATE }).strict()
24174
+ ]);
24175
+ var RevealCredentialSecretResponseSchema = external_exports.discriminatedUnion("credentialType", [
24176
+ external_exports.object({
24177
+ credentialType: external_exports.literal("USERNAME_PASSWORD"),
24178
+ username: external_exports.string(),
24179
+ password: external_exports.string()
24180
+ }).strict(),
24181
+ external_exports.object({
24182
+ credentialType: external_exports.enum([
24183
+ "BEARER_TOKEN",
24184
+ "JWT_TOKEN",
24185
+ "API_KEY",
24186
+ "PASSPHRASE",
24187
+ "COOKIE",
24188
+ "MAGIC_AUTH"
24189
+ ]),
24190
+ secretValue: external_exports.string()
24191
+ }).strict()
24192
+ ]);
23875
24193
 
23876
24194
  // ../../packages/api-contracts/src/contracts/v1/credential.ts
23877
24195
  var c8 = initContract();
@@ -23930,6 +24248,55 @@ var credentialContract = c8.router({
23930
24248
  500: ErrorResponseSchema
23931
24249
  }
23932
24250
  },
24251
+ // Replace the stored secret IN PLACE — same credentialId, new KMS envelope.
24252
+ // Separate from PATCH so every route touching KMS or plaintext is greppable
24253
+ // at its own URL. PUT (not PATCH) because the body carries the COMPLETE
24254
+ // secret for the type, so the same request twice yields the same end state.
24255
+ //
24256
+ // Deliberately NOT subject to the CREDENTIAL_IN_USE guard that blocks delete
24257
+ // and env-move during a live pentest: rotation preserves credentialId, so the
24258
+ // engagement's credential set and its ScanCredential rows are untouched —
24259
+ // which is the invariant that guard protects. Mid-engagement is precisely when
24260
+ // a wrong password needs fixing.
24261
+ rotateCredentialSecret: {
24262
+ method: "PUT",
24263
+ path: "/organizations/:organizationId/environments/:environmentId/credentials/:credentialId/secret",
24264
+ pathParams: CredentialIdParamsSchema,
24265
+ body: RotateCredentialSecretRequestSchema,
24266
+ responses: {
24267
+ // Unchanged response shape — a rotate NEVER echoes the secret back.
24268
+ 200: CredentialResponseSchema,
24269
+ 400: ErrorResponseSchema,
24270
+ 401: ErrorResponseSchema,
24271
+ 403: ErrorResponseSchema,
24272
+ 404: ErrorResponseSchema,
24273
+ // 422 — body's credentialType differs from the stored row's
24274
+ // (errorCode CREDENTIAL_ROTATE_TYPE_MISMATCH). Type changes stay
24275
+ // delete + recreate.
24276
+ 422: ErrorResponseSchema,
24277
+ 500: ErrorResponseSchema
24278
+ }
24279
+ },
24280
+ // Return the stored plaintext to an authorized human. POST, not GET: it must
24281
+ // never sit in an access log, a browser history entry, or a cacheable read.
24282
+ // Requires a user principal — API keys are rejected, so a leaked trace_sk_*
24283
+ // cannot exfiltrate customer credentials.
24284
+ revealCredentialSecret: {
24285
+ method: "POST",
24286
+ path: "/organizations/:organizationId/environments/:environmentId/credentials/:credentialId/reveal",
24287
+ pathParams: CredentialIdParamsSchema,
24288
+ body: c8.noBody(),
24289
+ responses: {
24290
+ 200: RevealCredentialSecretResponseSchema,
24291
+ 401: ErrorResponseSchema,
24292
+ 403: ErrorResponseSchema,
24293
+ 404: ErrorResponseSchema,
24294
+ // 422 — SESSION_STATE has no reveal variant by construction
24295
+ // (errorCode CREDENTIAL_REVEAL_UNSUPPORTED).
24296
+ 422: ErrorResponseSchema,
24297
+ 500: ErrorResponseSchema
24298
+ }
24299
+ },
23933
24300
  deleteCredential: {
23934
24301
  method: "DELETE",
23935
24302
  path: "/organizations/:organizationId/environments/:environmentId/credentials/:credentialId",
@@ -25714,7 +26081,7 @@ var CreateLinearTicketsParamsSchema = OrgIdParamsSchema.extend({
25714
26081
  });
25715
26082
  var CreateLinearTicketsRequestSchema = external_exports.object({
25716
26083
  /** UUID or PTF-N display id. Cap sized to the ~30s synchronous request budget. */
25717
- findingIds: external_exports.array(external_exports.string().min(1).max(64)).min(1).max(10),
26084
+ findingIds: external_exports.array(external_exports.string().min(1).max(64)).min(1).max(50),
25718
26085
  /** Single-finding requests only — a title with >1 finding is a 400. */
25719
26086
  title: external_exports.string().min(1).max(255).optional(),
25720
26087
  /** Destination overrides — ADMIN ONLY (MANAGE_ORGANIZATION); a non-admin sending any is a 403. */
@@ -25965,8 +26332,7 @@ var OrgCapabilityValues = [
25965
26332
  "SCAN_SECRETS",
25966
26333
  "SCAN_DAST",
25967
26334
  "AUTO_PR_SCANS",
25968
- "AUTO_PUSH_SCANS",
25969
- "AUTO_FIX_PR"
26335
+ "AUTO_PUSH_SCANS"
25970
26336
  ];
25971
26337
  var UpdateOrganizationRequestSchema = external_exports.object({
25972
26338
  name: external_exports.string().min(1).max(255).optional(),
@@ -26053,6 +26419,14 @@ var StaffOrganizationSummarySchema = external_exports.object({
26053
26419
  createdAt: external_exports.date()
26054
26420
  });
26055
26421
  var ListAllOrganizationsResponseSchema = external_exports.array(StaffOrganizationSummarySchema);
26422
+ var CreateOrganizationRequestSchema = external_exports.object({
26423
+ name: external_exports.string().trim().min(2, "Organization name must be at least 2 characters").max(100, "Organization name must be 100 characters or fewer")
26424
+ });
26425
+ var CreateOrganizationResponseSchema = external_exports.object({
26426
+ organizationId: external_exports.uuid(),
26427
+ slug: external_exports.string(),
26428
+ name: external_exports.string()
26429
+ });
26056
26430
  var OnboardingItemValues = [
26057
26431
  "CONNECT_CODE",
26058
26432
  "CONNECT_CLOUD",
@@ -26109,6 +26483,19 @@ var organizationContract = c20.router({
26109
26483
  },
26110
26484
  summary: "List every organization (Trace staff only)"
26111
26485
  },
26486
+ createOrganization: {
26487
+ method: "POST",
26488
+ path: "/staff/organizations",
26489
+ body: CreateOrganizationRequestSchema,
26490
+ responses: {
26491
+ 201: CreateOrganizationResponseSchema,
26492
+ 400: ErrorResponseSchema,
26493
+ 401: ErrorResponseSchema,
26494
+ 403: ErrorResponseSchema,
26495
+ 500: ErrorResponseSchema
26496
+ },
26497
+ summary: "Create an organization (Trace staff only)"
26498
+ },
26112
26499
  getOrganization: {
26113
26500
  method: "GET",
26114
26501
  path: "/organizations/:organizationId",
@@ -26525,10 +26912,189 @@ var CustomerPriorityCategoryValues = [
26525
26912
  ];
26526
26913
  var CustomerPriorityCategory = external_exports.enum(CustomerPriorityCategoryValues);
26527
26914
 
26915
+ // ../../packages/api-contracts/src/schemas/domain/pentest-customer-intake.schema.ts
26916
+ var IntakeSurfaceValues = [
26917
+ "WEB",
26918
+ "PUBLIC_API",
26919
+ "INTERNAL_API",
26920
+ "GRAPHQL",
26921
+ "WEBHOOKS",
26922
+ "MCP",
26923
+ "AI_AGENTS",
26924
+ "MOBILE",
26925
+ "DESKTOP",
26926
+ "CLI",
26927
+ "BACKGROUND_JOBS"
26928
+ ];
26929
+ var IntakeSurface = external_exports.enum(IntakeSurfaceValues);
26930
+ var PentestCustomerIntakeAnswersShape = external_exports.object({
26931
+ issueClasses: external_exports.array(CustomerPriorityCategory).max(11).optional(),
26932
+ issueClassesNotes: external_exports.string().max(5e3).optional(),
26933
+ focusAreas: external_exports.string().max(5e3).optional(),
26934
+ rolesAndPermissions: external_exports.string().max(1e4).optional(),
26935
+ authenticationSetup: external_exports.string().max(5e3).optional(),
26936
+ tenantIsolationExpectation: external_exports.string().max(5e3).optional(),
26937
+ surfaces: external_exports.array(IntakeSurface).max(11).optional(),
26938
+ surfacesNotes: external_exports.string().max(5e3).optional(),
26939
+ thirdParties: external_exports.string().max(5e3).optional(),
26940
+ sensitiveData: external_exports.string().max(5e3).optional(),
26941
+ neverTouch: external_exports.string().max(5e3).optional(),
26942
+ knownRisks: external_exports.string().max(5e3).optional(),
26943
+ tribalKnowledge: external_exports.string().max(1e4).optional()
26944
+ });
26945
+ var PentestCustomerIntakeAnswersSchema = PentestCustomerIntakeAnswersShape.strict();
26946
+ var PentestCustomerIntakeStoredAnswersSchema = PentestCustomerIntakeAnswersShape;
26947
+ var PentestCustomerIntakeWriteAnswersSchema = PentestCustomerIntakeAnswersShape.extend({
26948
+ /** @deprecated Retired question. Accepted for in-flight clients, never stored. */
26949
+ testEnvironment: external_exports.string().max(100).optional(),
26950
+ /** @deprecated Retired question. Accepted for in-flight clients, never stored. */
26951
+ reposAndBranches: external_exports.string().max(5e3).optional()
26952
+ }).strict();
26953
+ var PENTEST_CUSTOMER_INTAKE_ANSWER_KEYS = [
26954
+ "issueClasses",
26955
+ "issueClassesNotes",
26956
+ "focusAreas",
26957
+ "rolesAndPermissions",
26958
+ "authenticationSetup",
26959
+ "tenantIsolationExpectation",
26960
+ "surfaces",
26961
+ "surfacesNotes",
26962
+ "thirdParties",
26963
+ "sensitiveData",
26964
+ "neverTouch",
26965
+ "knownRisks",
26966
+ "tribalKnowledge"
26967
+ ];
26968
+ var PENTEST_CUSTOMER_INTAKE_LABELS = {
26969
+ issueClasses: "Issue classes to prioritize",
26970
+ issueClassesNotes: "Issue classes, additional notes",
26971
+ focusAreas: "Focus areas",
26972
+ rolesAndPermissions: "Roles and permissions",
26973
+ authenticationSetup: "Authentication setup",
26974
+ tenantIsolationExpectation: "Tenant isolation expectations",
26975
+ surfaces: "Surfaces in scope",
26976
+ surfacesNotes: "Surfaces, additional notes",
26977
+ thirdParties: "Third parties and integrations",
26978
+ sensitiveData: "Sensitive data",
26979
+ neverTouch: "Out of bounds / never touch",
26980
+ knownRisks: "Known risks",
26981
+ tribalKnowledge: "Tribal knowledge"
26982
+ };
26983
+ var PENTEST_CUSTOMER_INTAKE_QUESTIONS = [
26984
+ {
26985
+ kind: "multiSelect",
26986
+ key: "issueClasses",
26987
+ notesKey: "issueClassesNotes",
26988
+ question: "Which issue classes worry you most?",
26989
+ helper: "(authn bypass / broken access control / injection / data exposure / supply chain / infra exposure / multi-tenant isolation / crypto / business logic / SSRF / XSS)",
26990
+ notesQuestion: "Anything else about issue classes?"
26991
+ },
26992
+ {
26993
+ kind: "text",
26994
+ key: "focusAreas",
26995
+ question: "Is there any particular application or area you want us to spend extra focus on?"
26996
+ },
26997
+ {
26998
+ kind: "text",
26999
+ key: "rolesAndPermissions",
27000
+ question: "What roles/permissions can users take in your application?",
27001
+ helper: "Explain each role and what it should/shouldn't be able to do."
27002
+ },
27003
+ {
27004
+ kind: "text",
27005
+ key: "authenticationSetup",
27006
+ question: "Anything we should know about your authentication setup?",
27007
+ helper: "(e.g. mid-migration between providers, legacy login still live; otherwise we'll figure it out from the code)"
27008
+ },
27009
+ {
27010
+ kind: "text",
27011
+ key: "tenantIsolationExpectation",
27012
+ question: "What's your expectation for isolation between tenants/organizations?",
27013
+ helper: "What should never be reachable across that boundary?"
27014
+ },
27015
+ {
27016
+ kind: "multiSelect",
27017
+ key: "surfaces",
27018
+ notesKey: "surfacesNotes",
27019
+ question: "Which surfaces exist?",
27020
+ helper: "(web / public API / internal API / GraphQL / webhooks / MCP / AI agents / mobile / desktop / CLI / background jobs)",
27021
+ notesQuestion: "Anything else about your surfaces?"
27022
+ },
27023
+ {
27024
+ kind: "text",
27025
+ key: "thirdParties",
27026
+ question: "Which third parties do you connect to, and should we mock them or stand them up?",
27027
+ helper: "(e.g. CRMs, payment/bank APIs, identity providers, email/SMS, LLM providers)"
27028
+ },
27029
+ {
27030
+ kind: "text",
27031
+ key: "sensitiveData",
27032
+ question: "What data must no attacker ever access?",
27033
+ helper: "And what data do you not care about?"
27034
+ },
27035
+ {
27036
+ kind: "text",
27037
+ key: "neverTouch",
27038
+ question: "What must we never touch or trigger?",
27039
+ helper: "(destructive endpoints, real customer emails/SMS, third-party prod accounts, specific tenants)"
27040
+ },
27041
+ {
27042
+ kind: "text",
27043
+ key: "knownRisks",
27044
+ question: "What do you already know is broken or risky (i.e. what keeps you up at night)?"
27045
+ },
27046
+ {
27047
+ kind: "text",
27048
+ key: "tribalKnowledge",
27049
+ question: "What tribal knowledge would we only learn by asking?",
27050
+ helper: "Anything intentionally weird, counter-intuitive, or likely to trip us up; behavior that looks like a bug but isn't, dead code that's still deployed, a service that isn't what its name suggests."
27051
+ }
27052
+ ];
27053
+ var PENTEST_CUSTOMER_INTAKE_QUESTION_COUNT = PENTEST_CUSTOMER_INTAKE_QUESTIONS.length;
27054
+ var INTAKE_ISSUE_CLASS_LABELS = {
27055
+ AUTHN_BYPASS: "Authn bypass",
27056
+ AUTHZ_IDOR: "Broken access control",
27057
+ INJECTION: "Injection",
27058
+ DATA_EXPOSURE: "Data exposure",
27059
+ SUPPLY_CHAIN: "Supply chain",
27060
+ INFRA_EXPOSURE: "Infra exposure",
27061
+ MULTI_TENANT_ISOLATION: "Multi-tenant isolation",
27062
+ CRYPTO_WEAKNESS: "Crypto",
27063
+ BUSINESS_LOGIC: "Business logic",
27064
+ SSRF: "SSRF",
27065
+ XSS: "XSS"
27066
+ };
27067
+ var INTAKE_SURFACE_LABELS = {
27068
+ WEB: "Web",
27069
+ PUBLIC_API: "Public API",
27070
+ INTERNAL_API: "Internal API",
27071
+ GRAPHQL: "GraphQL",
27072
+ WEBHOOKS: "Webhooks",
27073
+ MCP: "MCP",
27074
+ AI_AGENTS: "AI agents",
27075
+ MOBILE: "Mobile",
27076
+ DESKTOP: "Desktop",
27077
+ CLI: "CLI",
27078
+ BACKGROUND_JOBS: "Background jobs"
27079
+ };
27080
+ function normalizePentestCustomerIntakeAnswers(answers) {
27081
+ const next = { ...answers };
27082
+ if (next.issueClasses !== void 0) {
27083
+ next.issueClasses = Array.from(new Set(next.issueClasses)).sort();
27084
+ }
27085
+ if (next.surfaces !== void 0) {
27086
+ next.surfaces = Array.from(new Set(next.surfaces)).sort();
27087
+ }
27088
+ return next;
27089
+ }
27090
+
26528
27091
  // ../../packages/api-contracts/src/schemas/api/v1/pentest.api.ts
26529
27092
  var PentestIdParamsSchema = OrgIdParamsSchema.extend({
26530
27093
  pentestId: external_exports.string().min(1)
26531
27094
  });
27095
+ var PentestApplicationParamsSchema = PentestIdParamsSchema.extend({
27096
+ applicationId: external_exports.uuid()
27097
+ });
26532
27098
  var CustomerPentestIdParamsSchema = OrgIdParamsSchema.extend({
26533
27099
  pentestId: PentestIdOrDisplayIdSchema
26534
27100
  });
@@ -26541,6 +27107,21 @@ var CustomerPentestStatusValues = [
26541
27107
  "PAUSED"
26542
27108
  ];
26543
27109
  var CustomerPentestStatus = external_exports.enum(CustomerPentestStatusValues);
27110
+ var BoundPentestApplicationSchema = external_exports.object({
27111
+ applicationId: external_exports.uuid(),
27112
+ name: external_exports.string(),
27113
+ type: external_exports.enum(ApplicationTypeValues),
27114
+ // `z.string()`, not `z.url()` — every other URL field on this surface's RESPONSE
27115
+ // side is a plain string. Writes already validate with `z.url()`
27116
+ // (`application.api.ts`), so re-validating stored data here buys nothing and would
27117
+ // let one legacy row 404 an entire staff page through `safeParse`.
27118
+ applicationUrl: external_exports.string().nullable(),
27119
+ /**
27120
+ * Bind order. Never renumbered on unbind, so gaps are normal and only the
27121
+ * relative order carries meaning — the lowest ordinal is the compat mirror.
27122
+ */
27123
+ ordinal: external_exports.number().int()
27124
+ });
26544
27125
  var PentestResponseSchema = external_exports.object({
26545
27126
  pentestId: external_exports.uuid(),
26546
27127
  organizationId: external_exports.uuid(),
@@ -26557,8 +27138,21 @@ var PentestResponseSchema = external_exports.object({
26557
27138
  scope: external_exports.string().nullable(),
26558
27139
  /** Granular stage state. Wizard surfaces this; worker transitions write it. */
26559
27140
  currentStage: PentestStage,
26560
- /** v1 supports one application per pentest. M:N (PentestApplication) deferred to v1.5. */
27141
+ /**
27142
+ * The LOWEST-ORDINAL bound application — a compat mirror of the authoritative
27143
+ * `PentestApplication` join, not a distinct concept. Read `applications` when you
27144
+ * need the engagement's real scope; a multi-application engagement reports its
27145
+ * first binding here and would otherwise understate what it covers.
27146
+ */
26561
27147
  primaryApplicationId: external_exports.uuid().nullable(),
27148
+ /**
27149
+ * Every bound application, ordinal-ascending. `.optional()` for rolling-deploy
27150
+ * safety: `apps/web` (Vercel) deploys ahead of `apps/api` (ECS), so a new web
27151
+ * build briefly reads responses from an API that does not send this yet — the
27152
+ * precedent is `PentestDetailResponseSchema` below. Absent means "this API is
27153
+ * older", NOT "no applications bound"; fall back to `primaryApplicationId`.
27154
+ */
27155
+ applications: external_exports.array(BoundPentestApplicationSchema).optional(),
26562
27156
  /** Customer threat-model context — free text. Used by hypothesis-gen prompt. */
26563
27157
  customerConcernsContext: external_exports.string().nullable(),
26564
27158
  customerPriorityCategories: external_exports.array(CustomerPriorityCategory),
@@ -26587,7 +27181,44 @@ var PentestDetailResponseSchema = PentestResponseSchema.extend({
26587
27181
  systemUnderTest: external_exports.string().optional(),
26588
27182
  testingWindowStart: external_exports.coerce.date().nullable().optional(),
26589
27183
  testingWindowEnd: external_exports.coerce.date().nullable().optional(),
26590
- engagementMetadataOverrides: EngagementMetadataOverridesSchema.optional()
27184
+ engagementMetadataOverrides: EngagementMetadataOverridesSchema.optional(),
27185
+ /**
27186
+ * True once the engagement's repository scope has been snapshotted, which
27187
+ * PERMANENTLY refuses bind and unbind (`repositoryScopes > 0` in
27188
+ * `requireBindableInLock`).
27189
+ *
27190
+ * This one refusal only — that guard also refuses on `PENTEST_CLOSED` and on a
27191
+ * non-scoping stage, both of which the client can already read off `status` and
27192
+ * `currentStage` on this same response. So `false` means "not frozen by the
27193
+ * snapshot", NOT "a bind will succeed"; a consumer that gates a control must check
27194
+ * the stage and closed state too.
27195
+ *
27196
+ * Needed as its OWN field because nothing else on the wire answers it: the scope
27197
+ * freezes at the first scope-consuming session create, and only VALIDATION moves
27198
+ * the stage, so RECON / CODE_REVIEW / PRODUCT_RECON leave the engagement in
27199
+ * `DRAFT` with permanently frozen bindings. `getRepositoryScope` reports
27200
+ * `source: 'PENTEST_SCOPE'` only PAST the scoping stages, so a frozen `DRAFT`
27201
+ * engagement still returns live application links there.
27202
+ *
27203
+ * Scoped to BINDINGS, not to the whole target: environment add/remove is gated on
27204
+ * stage alone (`PentestEnvironmentService`, `NON_SCOPING_PENTEST_STAGES`), and
27205
+ * `DRAFT` is a scoping stage — so a consumer must not disable environment editing
27206
+ * on this flag. Doing so would remove the one provisioning action a frozen
27207
+ * multi-application engagement still legitimately needs.
27208
+ *
27209
+ * Detail-only (the list schema deliberately carries no per-pentest aggregates) and
27210
+ * `.optional()` for the same rolling-deploy reason as the fields above.
27211
+ */
27212
+ applicationBindingFrozen: external_exports.boolean().optional(),
27213
+ /**
27214
+ * Optional customer intake questionnaire answers. Staff detail only.
27215
+ * `.optional()` for rolling-deploy safety (Vercel ahead of ECS).
27216
+ *
27217
+ * Read with the lenient schema for the same reason: an API that hasn't
27218
+ * deployed yet still answers with keys from a retired question, and a strict
27219
+ * parse here would fail the whole response instead of ignoring them.
27220
+ */
27221
+ customerIntakeAnswers: PentestCustomerIntakeStoredAnswersSchema.nullable().optional()
26591
27222
  });
26592
27223
  var CustomerPentestShellResponseSchema = external_exports.object({
26593
27224
  pentestId: external_exports.uuid(),
@@ -26596,6 +27227,24 @@ var CustomerPentestShellResponseSchema = external_exports.object({
26596
27227
  title: external_exports.string(),
26597
27228
  description: external_exports.string().nullable(),
26598
27229
  customerStatus: CustomerPentestStatus,
27230
+ /** The LOWEST-ORDINAL application under test — a compat mirror of the join. Null =
27231
+ * engagement not bound to an application. `.optional()` for rolling-deploy
27232
+ * safety — apps/web (Vercel) deploys ahead of apps/api (ECS).
27233
+ *
27234
+ * Read `applicationIds` for the homepage "In scope" chips: this field names one
27235
+ * application, so using it there marked the other bindings of a multi-application
27236
+ * engagement "Not in current scope" — a false statement on a customer surface. */
27237
+ primaryApplicationId: external_exports.uuid().nullable().optional(),
27238
+ /**
27239
+ * EVERY application this engagement covers, in bind order. Ids only — the customer
27240
+ * homepage already has the org's applications and resolves names locally, and this
27241
+ * surface deliberately shows no more application detail than it did.
27242
+ *
27243
+ * `.optional()` for the same rolling-deploy reason: absent means "this API is
27244
+ * older", NOT "nothing bound", so a consumer must fall back to
27245
+ * `primaryApplicationId` rather than claim the engagement covers nothing.
27246
+ */
27247
+ applicationIds: external_exports.array(external_exports.uuid()).optional(),
26599
27248
  startedAt: external_exports.coerce.date().nullable(),
26600
27249
  endedAt: external_exports.coerce.date().nullable(),
26601
27250
  createdAt: external_exports.coerce.date(),
@@ -26614,8 +27263,35 @@ var ListCustomerPentestsResponseSchema = external_exports.array(CustomerPentestR
26614
27263
  var CustomerPentestDetailResponseSchema = CustomerPentestResponseSchema.extend({
26615
27264
  systemUnderTest: external_exports.string().optional(),
26616
27265
  testingWindowStart: external_exports.coerce.date().nullable().optional(),
26617
- testingWindowEnd: external_exports.coerce.date().nullable().optional()
27266
+ testingWindowEnd: external_exports.coerce.date().nullable().optional(),
27267
+ /**
27268
+ * Discovery flags for the questionnaire header CTA. Answer bodies live on
27269
+ * GET …/questionnaire. `.optional()` for rolling-deploy safety.
27270
+ */
27271
+ questionnaireEditable: external_exports.boolean().optional(),
27272
+ hasQuestionnaireAnswers: external_exports.boolean().optional()
26618
27273
  });
27274
+ var CustomerPentestQuestionnaireResponseSchema = external_exports.object({
27275
+ pentestId: external_exports.uuid(),
27276
+ /** True iff currentStage ∈ SCOPING_PENTEST_STAGES (DRAFT | READY_TO_START). */
27277
+ editable: external_exports.boolean(),
27278
+ /**
27279
+ * Lenient on unknown keys: during a rolling deploy the dashboard runs against
27280
+ * an API that still answers with a retired question's key, and a strict parse
27281
+ * would turn that into a broken page instead of an ignored field. Writes stay
27282
+ * strict — see the upsert request below.
27283
+ */
27284
+ answers: PentestCustomerIntakeStoredAnswersSchema.nullable(),
27285
+ updatedAt: external_exports.coerce.date()
27286
+ });
27287
+ var UpsertCustomerPentestQuestionnaireRequestSchema = external_exports.object({
27288
+ /**
27289
+ * Full answers object, or null to clear. Keys of retired questions are
27290
+ * accepted and dropped so a tab opened before the deploy can still save;
27291
+ * keys that were never questions are still rejected.
27292
+ */
27293
+ answers: PentestCustomerIntakeWriteAnswersSchema.nullable()
27294
+ }).strict();
26619
27295
  var ReadPentestResponseSchema = external_exports.union([
26620
27296
  PentestDetailResponseSchema,
26621
27297
  CustomerPentestShellResponseSchema
@@ -26675,6 +27351,14 @@ var UpdatePentestRequestSchema = external_exports.object({
26675
27351
  scope: external_exports.string().max(5e4).nullable().optional(),
26676
27352
  startedAt: external_exports.coerce.date().nullable().optional(),
26677
27353
  endedAt: external_exports.coerce.date().nullable().optional(),
27354
+ /**
27355
+ * SINGLE-APPLICATION COMPAT PATH ONLY: means "replace the bound set with exactly
27356
+ * this application", or clear it on `null`. Rejected with 409
27357
+ * `PENTEST_MULTI_APPLICATION_USE_BIND_ENDPOINT` when 2+ applications are already
27358
+ * bound — otherwise one PATCH would silently unbind N−1 of them and bypass the
27359
+ * per-application unbind guard. Use `bindApplication` / `unbindApplication` for
27360
+ * anything multi-application.
27361
+ */
26678
27362
  primaryApplicationId: external_exports.uuid().nullable().optional(),
26679
27363
  customerConcernsContext: external_exports.string().max(2e4).nullable().optional(),
26680
27364
  customerPriorityCategories: external_exports.array(CustomerPriorityCategory).max(20).optional(),
@@ -26688,6 +27372,9 @@ var UpdatePentestRequestSchema = external_exports.object({
26688
27372
  perHypothesisBudgetCapUsd: external_exports.number().min(0).max(1e5).optional(),
26689
27373
  lifecycleReason: external_exports.string().trim().min(1).max(2e3).optional()
26690
27374
  });
27375
+ var BindPentestApplicationRequestSchema = external_exports.object({
27376
+ applicationId: external_exports.uuid()
27377
+ }).strict();
26691
27378
 
26692
27379
  // ../../packages/api-contracts/src/schemas/api/v1/pentest-stage-transition.api.ts
26693
27380
  var StageTransitionResponseSchema = external_exports.object({
@@ -26793,6 +27480,33 @@ var pentestContract = c21.router({
26793
27480
  500: ErrorResponseSchema
26794
27481
  }
26795
27482
  },
27483
+ getCustomerPentestQuestionnaire: {
27484
+ method: "GET",
27485
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/questionnaire",
27486
+ pathParams: CustomerPentestIdParamsSchema,
27487
+ responses: {
27488
+ 200: CustomerPentestQuestionnaireResponseSchema,
27489
+ 401: ErrorResponseSchema,
27490
+ 403: ErrorResponseSchema,
27491
+ 404: ErrorResponseSchema,
27492
+ 500: ErrorResponseSchema
27493
+ }
27494
+ },
27495
+ upsertCustomerPentestQuestionnaire: {
27496
+ method: "PUT",
27497
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/questionnaire",
27498
+ pathParams: CustomerPentestIdParamsSchema,
27499
+ body: UpsertCustomerPentestQuestionnaireRequestSchema,
27500
+ responses: {
27501
+ 200: CustomerPentestQuestionnaireResponseSchema,
27502
+ 400: ErrorResponseSchema,
27503
+ 401: ErrorResponseSchema,
27504
+ 403: ErrorResponseSchema,
27505
+ 404: ErrorResponseSchema,
27506
+ 409: ErrorResponseSchema,
27507
+ 500: ErrorResponseSchema
27508
+ }
27509
+ },
26796
27510
  createPentest: {
26797
27511
  method: "POST",
26798
27512
  path: "/organizations/:organizationId/pentests",
@@ -26849,6 +27563,49 @@ var pentestContract = c21.router({
26849
27563
  500: ErrorResponseSchema
26850
27564
  }
26851
27565
  },
27566
+ // POST/DELETE-per-row rather than a batch set on PATCH, mirroring
27567
+ // `pentest-environment.ts`: every rejection stays attributable to ONE
27568
+ // application, where a set-diff would fail an entire save on one blocked
27569
+ // removal with no way to say which.
27570
+ bindApplication: {
27571
+ method: "POST",
27572
+ path: "/organizations/:organizationId/pentests/:pentestId/applications",
27573
+ pathParams: PentestIdParamsSchema,
27574
+ body: BindPentestApplicationRequestSchema,
27575
+ responses: {
27576
+ // The resulting bound set, NOT the detail shape. The 201 body is assembled purely
27577
+ // inside the bind transaction, so it cannot describe a state the write did not
27578
+ // produce; deriving the engagement cover here would mean a fallible read AFTER the
27579
+ // commit. Callers that need the cover GET the pentest.
27580
+ 201: PentestResponseSchema,
27581
+ 400: ErrorResponseSchema,
27582
+ 401: ErrorResponseSchema,
27583
+ 403: ErrorResponseSchema,
27584
+ 404: ErrorResponseSchema,
27585
+ // 409 — PENTEST_APPLICATION_ALREADY_BOUND (unique violation),
27586
+ // PENTEST_APPLICATION_FROZEN_AT_STAGE (past scoping), or
27587
+ // PENTEST_APPLICATION_SCOPE_FROZEN (repository scope already snapshotted,
27588
+ // so a new binding could never enter it).
27589
+ 409: ErrorResponseSchema,
27590
+ 500: ErrorResponseSchema
27591
+ }
27592
+ },
27593
+ unbindApplication: {
27594
+ method: "DELETE",
27595
+ path: "/organizations/:organizationId/pentests/:pentestId/applications/:applicationId",
27596
+ pathParams: PentestApplicationParamsSchema,
27597
+ responses: {
27598
+ 204: DeleteResponseSchema,
27599
+ 400: ErrorResponseSchema,
27600
+ 401: ErrorResponseSchema,
27601
+ 403: ErrorResponseSchema,
27602
+ 404: ErrorResponseSchema,
27603
+ // 409 — PENTEST_APPLICATION_UNBIND_BLOCKED (this application still has
27604
+ // linked environments), or either frozen guard as on bind.
27605
+ 409: ErrorResponseSchema,
27606
+ 500: ErrorResponseSchema
27607
+ }
27608
+ },
26852
27609
  getScanReuseSuggestions: {
26853
27610
  method: "GET",
26854
27611
  path: "/organizations/:organizationId/pentests/:pentestId/scan-reuse-suggestions",
@@ -27427,7 +28184,22 @@ var ScopeRowSchema = external_exports.object({
27427
28184
  type: ScopeRowType,
27428
28185
  label: external_exports.string().min(1).max(200),
27429
28186
  description: external_exports.string().max(500),
27430
- inScope: external_exports.boolean()
28187
+ inScope: external_exports.boolean(),
28188
+ /**
28189
+ * Which bound applications this asset belongs to — DISPLAY NAMES in bind order
28190
+ * (multi-application PR 7).
28191
+ *
28192
+ * A SNAPSHOT, like `PentestRepositoryScope.purposeSnapshot`, not a reference: the
28193
+ * report row is a frozen customer-facing document, and resolving application ids at
28194
+ * render time would let a regenerate after an unbind silently shrink a published
28195
+ * scope column. A name that no longer matches a bound application is therefore
28196
+ * legitimate history, not an error.
28197
+ *
28198
+ * Absent or empty means "provenance unknown" — the renderer omits the cell rather
28199
+ * than printing "none" (the PR 1b hand-off rule, shared with the session briefs).
28200
+ * Capped at the row cap so it can never be the binding constraint below it.
28201
+ */
28202
+ applications: external_exports.array(external_exports.string().min(1).max(200)).max(100).optional()
27431
28203
  });
27432
28204
  var ScopeRowsJsonSchema = external_exports.object({
27433
28205
  version: external_exports.literal(1),
@@ -27650,7 +28422,13 @@ var ReportCoverBlockSchema = external_exports.object({
27650
28422
  testingWindowStart: NullableCoverFieldSchema,
27651
28423
  testingWindowEnd: NullableCoverFieldSchema,
27652
28424
  classification: CoverFieldSchema,
27653
- documentId: CoverFieldSchema
28425
+ documentId: CoverFieldSchema,
28426
+ /** Report attribution (derive-with-override like the rest; the derived
28427
+ * value is a firm-default constant, so `value` is always present).
28428
+ * testerName renders as "<name> · <firm>" in the cover's Tested-by row;
28429
+ * approverName is the "Approved by" row (conventionally "Name, Title"). */
28430
+ testerName: CoverFieldSchema,
28431
+ approverName: CoverFieldSchema
27654
28432
  });
27655
28433
  var ReportCoverFieldNameValues = [
27656
28434
  "customerOrgName",
@@ -27660,7 +28438,9 @@ var ReportCoverFieldNameValues = [
27660
28438
  "testingWindowStart",
27661
28439
  "testingWindowEnd",
27662
28440
  "classification",
27663
- "documentId"
28441
+ "documentId",
28442
+ "testerName",
28443
+ "approverName"
27664
28444
  ];
27665
28445
  var ReportCoverFieldName = external_exports.enum(ReportCoverFieldNameValues);
27666
28446
  var ApplyReportCoverRequestSchema = external_exports.object({
@@ -27719,6 +28499,20 @@ var ReportCanvasResponseSchema = external_exports.object({
27719
28499
  /** Per-field override provenance for the cover grid (RS2). */
27720
28500
  cover: ReportCoverBlockSchema,
27721
28501
  scopeRows: external_exports.array(ScopeRowSchema),
28502
+ /**
28503
+ * Every bound application's name, in bind order — what the scope editor offers as
28504
+ * per-row application toggles (multi-application PR 7). Empty for an unbound
28505
+ * engagement; a single entry means the editor stays hidden, matching the report's
28506
+ * own rule that one application needs no attribution column.
28507
+ *
28508
+ * OPTIONAL for the wire, not because the server ever omits it: `apps/web` deploys on
28509
+ * merge while the API deploys after it, and the ts-rest client does not validate
28510
+ * response bodies (`apps/web/lib/api-client.ts` sets no `validateResponse`), so a new
28511
+ * bundle briefly reads an old API's response. A required field would be typed as
28512
+ * present while arriving `undefined` — the same reason
28513
+ * `AgentSessionSpawnContextSchema.applications` is optional. Consumers default it.
28514
+ */
28515
+ boundApplications: external_exports.array(external_exports.string()).optional(),
27722
28516
  /** null = undrafted, or stored JSON was unparseable (degraded — never 500). */
27723
28517
  narrative: StoredNarrativeJsonSchema.nullable(),
27724
28518
  /** Render Section 05 (Remediation Timeline) in the PDF. Lazy/no-row previews
@@ -27862,6 +28656,11 @@ var IssuedReportSnapshotSchema = external_exports.object({
27862
28656
  * pre-feature `input_json` still parses (absent → undefined → standard
27863
28657
  * branding). */
27864
28658
  coBrandName: external_exports.string().nullable().optional(),
28659
+ /** Report attribution frozen at issuance. Optional so every pre-feature
28660
+ * `input_json` still parses (absent → legacy rendering: firm-only
28661
+ * Tested-by row, no Approved-by row). */
28662
+ testerName: external_exports.string().optional(),
28663
+ approverName: external_exports.string().optional(),
27865
28664
  engagementTitle: external_exports.string(),
27866
28665
  engagementSubtitle: external_exports.string(),
27867
28666
  systemUnderTest: external_exports.string(),
@@ -28189,7 +28988,18 @@ var PentestFindingResponseSchema = external_exports.object({
28189
28988
  pentestFindingId: external_exports.uuid(),
28190
28989
  organizationId: external_exports.uuid(),
28191
28990
  pentestId: external_exports.uuid(),
28192
- displayId: external_exports.number(),
28991
+ /**
28992
+ * Customer-facing finding number (the N in `PTF-N`). NULL until the finding is
28993
+ * published: a finding never shown to a customer has no customer-facing number yet.
28994
+ * Staff and agents identify an unpublished finding by `pentestFindingId` + `title`.
28995
+ *
28996
+ * Nullable here so it cascades to the staff and AGENT derivations below — every
28997
+ * agent-session type except REPORT reads findings with no visibility filter
28998
+ * (agent-session.service.ts operatorListFindings/operatorGetFinding), so a draft
28999
+ * reaches them and a non-null schema would be a Zod 500. The customer derivations
29000
+ * re-narrow it to non-null, because customer visibility implies published.
29001
+ */
29002
+ displayId: external_exports.number().int().positive().nullable(),
28193
29003
  title: external_exports.string(),
28194
29004
  description: external_exports.string(),
28195
29005
  severity: PentestFindingSeverity,
@@ -28263,9 +29073,11 @@ var OperatorFindingReadSchema = PentestFindingResponseSchema.omit({
28263
29073
  externalTicket: true
28264
29074
  });
28265
29075
  var OperatorFindingRetestSchema = external_exports.object({
29076
+ retestId: external_exports.uuid(),
28266
29077
  outcome: RetestOutcome,
28267
29078
  notes: external_exports.string().nullable(),
28268
29079
  testedAt: external_exports.coerce.date(),
29080
+ editedAt: external_exports.coerce.date().nullable(),
28269
29081
  source: PentestFindingRetestSource
28270
29082
  });
28271
29083
  var OperatorFindingDetailSchema = OperatorFindingReadSchema.extend({
@@ -28295,10 +29107,20 @@ var CustomerPentestFindingResponseSchema = PentestFindingResponseSchema.omit({
28295
29107
  reportProseAt: true,
28296
29108
  reportProseStatus: true
28297
29109
  }).extend({
29110
+ // Customer findings are published-only (customerVisiblePentestFindingWhere), and
29111
+ // publishing is what assigns the number, so it always exists here. Re-narrowed from
29112
+ // the nullable root so the customer dashboard never needs a draft affordance; the
29113
+ // handler mapper throws if the invariant is ever violated rather than coercing.
29114
+ displayId: external_exports.number().int().positive(),
28298
29115
  // Customer-safe prose/rendering status. This preserves report/PDF heading
28299
29116
  // semantics without exposing the staff-only provenance marker pair.
28300
29117
  renderedStatus: PentestFindingStatus,
28301
- repositories: external_exports.array(CustomerPentestFindingRepositorySchema)
29118
+ repositories: external_exports.array(CustomerPentestFindingRepositorySchema),
29119
+ // True when the customer has an unresolved dispute on this finding. Drives the
29120
+ // customer "dispute under review" hint + suppresses the re-dispute affordance,
29121
+ // even after a retest moved the finding off CONTESTED. Only this derived
29122
+ // boolean crosses the trust boundary — never the staff contest list.
29123
+ hasOpenContest: external_exports.boolean()
28302
29124
  });
28303
29125
  var CustomerPentestFindingListItemSchema = PentestFindingListItemSchema.omit({
28304
29126
  organizationId: true,
@@ -28310,6 +29132,9 @@ var CustomerPentestFindingListItemSchema = PentestFindingListItemSchema.omit({
28310
29132
  reportProseAt: true,
28311
29133
  reportProseStatus: true
28312
29134
  }).extend({
29135
+ // Published-only, same as the customer detail schema above — re-narrowed from the
29136
+ // nullable root.
29137
+ displayId: external_exports.number().int().positive(),
28313
29138
  repositories: external_exports.array(CustomerPentestFindingRepositorySchema)
28314
29139
  });
28315
29140
  var ListCustomerPentestFindingsResponseSchema = external_exports.array(
@@ -28455,7 +29280,12 @@ var RecoveredFindingResultSchema = external_exports.object({
28455
29280
  status: external_exports.enum(["recovered", "skipped"]),
28456
29281
  reason: external_exports.string().optional(),
28457
29282
  pentestFindingId: external_exports.uuid().optional(),
28458
- findingDisplayId: external_exports.int().optional(),
29283
+ /**
29284
+ * NULL when the recovered finding has no number yet. Recovered findings land
29285
+ * UNPUBLISHED (see the docstring above), and publishing is what assigns the number,
29286
+ * so `.optional()` alone would reject the `null` the service returns.
29287
+ */
29288
+ findingDisplayId: external_exports.int().positive().nullish(),
28459
29289
  /** Preview-only (dryRun): the title + severity the finding WOULD get. */
28460
29290
  findingTitle: external_exports.string().optional(),
28461
29291
  severity: PentestFindingSeverity.nullable().optional(),
@@ -28552,8 +29382,9 @@ var PentestFindingContestResponseSchema = external_exports.object({
28552
29382
  resolvedById: external_exports.uuid().nullable(),
28553
29383
  resolvedByName: external_exports.string().nullable(),
28554
29384
  resolvedAt: external_exports.coerce.date().nullable(),
28555
- // The finding status the resolution moved the finding to (PR 3): OPEN =
28556
- // dispute rejected; DISMISSED / ACCEPTED_WITH_CONTROLS = upheld. NULL while open.
29385
+ // The finding's ACTUAL status at resolution time (any active status for a
29386
+ // status-neutral reject). DISMISSED / ACCEPTED_WITH_CONTROLS ⇔ the dispute
29387
+ // was accepted; anything else ⇔ rejected. NULL while open.
28557
29388
  resultStatus: PentestFindingStatus.nullable()
28558
29389
  });
28559
29390
  var ListPentestFindingContestsResponseSchema = external_exports.array(
@@ -28578,6 +29409,9 @@ var PentestFindingRetestResponseSchema = external_exports.object({
28578
29409
  testedById: external_exports.uuid(),
28579
29410
  testedByName: external_exports.string(),
28580
29411
  testedAt: external_exports.coerce.date(),
29412
+ // Null = never corrected. Staff-only: the customer schema below is an explicit
29413
+ // allowlist, so this cannot reach a customer by omission.
29414
+ editedAt: external_exports.coerce.date().nullable(),
28581
29415
  source: PentestFindingRetestSource,
28582
29416
  retestRunId: external_exports.uuid().nullable(),
28583
29417
  performedByActorType: PentestAuditActorType,
@@ -29024,7 +29858,8 @@ var pentestFindingContract = c26.router({
29024
29858
  401: ErrorResponseSchema,
29025
29859
  403: ErrorResponseSchema,
29026
29860
  404: ErrorResponseSchema,
29027
- // CONTEST_ALREADY_RESOLVED, non-CONTESTED finding, or PENTEST_CLOSED.
29861
+ // CONTEST_ALREADY_RESOLVED, CONTEST_REJECT_NOT_ALLOWED (reject on a
29862
+ // dismissed/accepted finding), or PENTEST_CLOSED.
29028
29863
  409: ErrorResponseSchema,
29029
29864
  500: ErrorResponseSchema
29030
29865
  }
@@ -30000,28 +30835,6 @@ var repositoryContract = c31.router({
30000
30835
  409: ErrorResponseSchema,
30001
30836
  500: ErrorResponseSchema
30002
30837
  }
30003
- },
30004
- /**
30005
- * Set / clear the per-repo AI coding-agent override (PR 5).
30006
- * Body `{overrideTool}`: 'claude-code'|'codex' sets, `null` clears.
30007
- * Response `{aiToolingProfile}` is nullable: clearing on an undetected
30008
- * repo writes NULL to the column and returns `{aiToolingProfile: null}`.
30009
- * 422 = `PLATFORM_VALIDATION_UNSUPPORTED` for non-GITHUB platforms.
30010
- */
30011
- setAiToolingProfileOverride: {
30012
- method: "PATCH",
30013
- path: "/organizations/:organizationId/repositories/:repositoryId/ai-tooling-profile",
30014
- pathParams: RepositoryIdParamsSchema,
30015
- body: SetAiToolingProfileOverrideRequestSchema,
30016
- responses: {
30017
- 200: SetAiToolingProfileOverrideResponseSchema,
30018
- 400: ErrorResponseSchema,
30019
- 401: ErrorResponseSchema,
30020
- 403: ErrorResponseSchema,
30021
- 404: ErrorResponseSchema,
30022
- 422: ErrorResponseSchema,
30023
- 500: ErrorResponseSchema
30024
- }
30025
30838
  }
30026
30839
  });
30027
30840
 
@@ -31051,122 +31864,6 @@ var vulnerabilityContract = c36.router({
31051
31864
  }
31052
31865
  });
31053
31866
 
31054
- // ../../packages/api-contracts/src/schemas/api/v1/vulnerability-fix-run.api.ts
31055
- var AGENT_FLAVORS = ["claude-code", "codex"];
31056
- var VULNERABILITY_FIX_RUN_STATUSES = [
31057
- "QUEUED",
31058
- "RUNNING",
31059
- "ARTIFACTS_READY",
31060
- "NO_SAFE_FIX",
31061
- "PATCH_TOO_LARGE",
31062
- "TESTS_FAILED",
31063
- "PR_OPENED",
31064
- "INFRA_FAILED",
31065
- "STALE_VULN",
31066
- "TIMED_OUT",
31067
- "CANCELLED"
31068
- ];
31069
- var VulnerabilityFixRunPathParamsSchema = VulnerabilityLookupIdParamsSchema.extend({
31070
- runId: external_exports.uuid()
31071
- });
31072
- var TriggerFixRunRequestSchema = external_exports.object({
31073
- agentFlavor: external_exports.enum(AGENT_FLAVORS).optional()
31074
- });
31075
- var VulnerabilityFixRunResponseSchema = external_exports.object({
31076
- runId: external_exports.uuid(),
31077
- organizationId: external_exports.uuid(),
31078
- vulnerabilityId: external_exports.uuid(),
31079
- repositoryId: external_exports.uuid(),
31080
- status: external_exports.enum(VULNERABILITY_FIX_RUN_STATUSES),
31081
- agentFlavor: external_exports.enum(AGENT_FLAVORS),
31082
- triggeredByUserId: external_exports.uuid().nullable(),
31083
- triggeredByApiKeyId: external_exports.uuid().nullable(),
31084
- /**
31085
- * Display name + avatar of the triggering staff user, resolved
31086
- * server-side (mirrors `ScanResponseSchema.initiatedByName`). Populated by
31087
- * every endpoint (list, trigger, get, cancel) so the polled active row in
31088
- * the runs table keeps the resolved name. `null` only when the run has no
31089
- * triggering user (api-key path) or the user no longer exists.
31090
- */
31091
- triggeredByName: external_exports.string().nullable(),
31092
- triggeredByProfilePictureUrl: external_exports.string().nullable(),
31093
- prUrl: external_exports.string().nullable(),
31094
- summaryStatus: external_exports.string().nullable(),
31095
- modelUsed: external_exports.string().nullable(),
31096
- addedLines: external_exports.number().int().nullable(),
31097
- removedLines: external_exports.number().int().nullable(),
31098
- filesChanged: external_exports.number().int().nullable(),
31099
- statusDetails: external_exports.string().nullable(),
31100
- queuedAt: external_exports.iso.datetime(),
31101
- claimedAt: external_exports.iso.datetime().nullable(),
31102
- deadlineAt: external_exports.iso.datetime().nullable(),
31103
- completedAt: external_exports.iso.datetime().nullable(),
31104
- updatedAt: external_exports.iso.datetime()
31105
- });
31106
- var ListVulnerabilityFixRunsResponseSchema = external_exports.array(VulnerabilityFixRunResponseSchema);
31107
-
31108
- // ../../packages/api-contracts/src/contracts/v1/vulnerability-fix-run.ts
31109
- var c37 = initContract();
31110
- var vulnerabilityFixRunContract = c37.router({
31111
- triggerRun: {
31112
- method: "POST",
31113
- path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/fix-pr-runs",
31114
- pathParams: VulnerabilityLookupIdParamsSchema,
31115
- body: TriggerFixRunRequestSchema,
31116
- responses: {
31117
- 201: VulnerabilityFixRunResponseSchema,
31118
- 400: ErrorResponseSchema,
31119
- 401: ErrorResponseSchema,
31120
- 403: ErrorResponseSchema,
31121
- 404: ErrorResponseSchema,
31122
- 409: ErrorResponseSchema,
31123
- 429: ErrorResponseSchema,
31124
- 500: ErrorResponseSchema
31125
- }
31126
- },
31127
- getRun: {
31128
- method: "GET",
31129
- path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/fix-pr-runs/:runId",
31130
- pathParams: VulnerabilityFixRunPathParamsSchema,
31131
- responses: {
31132
- 200: VulnerabilityFixRunResponseSchema,
31133
- 400: ErrorResponseSchema,
31134
- 401: ErrorResponseSchema,
31135
- 403: ErrorResponseSchema,
31136
- 404: ErrorResponseSchema,
31137
- 500: ErrorResponseSchema
31138
- }
31139
- },
31140
- cancelRun: {
31141
- method: "POST",
31142
- path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/fix-pr-runs/:runId/cancel",
31143
- pathParams: VulnerabilityFixRunPathParamsSchema,
31144
- body: null,
31145
- responses: {
31146
- 200: VulnerabilityFixRunResponseSchema,
31147
- 400: ErrorResponseSchema,
31148
- 401: ErrorResponseSchema,
31149
- 403: ErrorResponseSchema,
31150
- 404: ErrorResponseSchema,
31151
- 409: ErrorResponseSchema,
31152
- 500: ErrorResponseSchema
31153
- }
31154
- },
31155
- listRuns: {
31156
- method: "GET",
31157
- path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/fix-pr-runs",
31158
- pathParams: VulnerabilityLookupIdParamsSchema,
31159
- responses: {
31160
- 200: ListVulnerabilityFixRunsResponseSchema,
31161
- 400: ErrorResponseSchema,
31162
- 401: ErrorResponseSchema,
31163
- 403: ErrorResponseSchema,
31164
- 404: ErrorResponseSchema,
31165
- 500: ErrorResponseSchema
31166
- }
31167
- }
31168
- });
31169
-
31170
31867
  // ../../packages/api-contracts/src/schemas/api/v1/vulnerability-note.api.ts
31171
31868
  var VulnerabilityLookupNoteParamsSchema = OrgIdParamsSchema.extend({
31172
31869
  vulnerabilityId: VulnerabilityLookupIdSchema
@@ -31194,8 +31891,8 @@ var UpdateVulnerabilityNoteRequestSchema = external_exports.object({
31194
31891
  });
31195
31892
 
31196
31893
  // ../../packages/api-contracts/src/contracts/v1/vulnerability-note.ts
31197
- var c38 = initContract();
31198
- var vulnerabilityNoteContract = c38.router({
31894
+ var c37 = initContract();
31895
+ var vulnerabilityNoteContract = c37.router({
31199
31896
  listNotes: {
31200
31897
  method: "GET",
31201
31898
  path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/notes",
@@ -31376,8 +32073,8 @@ var ListWebhookDeliveriesResponseSchema = external_exports.object({
31376
32073
  });
31377
32074
 
31378
32075
  // ../../packages/api-contracts/src/contracts/v1/webhook-settings.ts
31379
- var c39 = initContract();
31380
- var webhookSettingsContract = c39.router({
32076
+ var c38 = initContract();
32077
+ var webhookSettingsContract = c38.router({
31381
32078
  listWebhookEndpoints: {
31382
32079
  method: "GET",
31383
32080
  path: "/organizations/:organizationId/webhook-settings/endpoints",
@@ -31515,8 +32212,8 @@ var webhookSettingsContract = c39.router({
31515
32212
  });
31516
32213
 
31517
32214
  // ../../packages/api-contracts/src/contracts/v1/index.ts
31518
- var c40 = initContract();
31519
- var v1Contract = c40.router(
32215
+ var c39 = initContract();
32216
+ var v1Contract = c39.router(
31520
32217
  {
31521
32218
  admin: adminContract,
31522
32219
  cloudIntegration: cloudIntegrationContract,
@@ -31536,7 +32233,6 @@ var v1Contract = c40.router(
31536
32233
  knowledgeDocument: knowledgeDocumentContract,
31537
32234
  vulnerability: vulnerabilityContract,
31538
32235
  vulnerabilityNote: vulnerabilityNoteContract,
31539
- vulnerabilityFixRun: vulnerabilityFixRunContract,
31540
32236
  webhookSettings: webhookSettingsContract,
31541
32237
  application: applicationContract,
31542
32238
  chatSession: chatSessionContract,
@@ -31565,8 +32261,8 @@ var v1Contract = c40.router(
31565
32261
  );
31566
32262
 
31567
32263
  // ../../packages/api-contracts/src/contracts/index.ts
31568
- var c41 = initContract();
31569
- var contract = c41.router({
32264
+ var c40 = initContract();
32265
+ var contract = c40.router({
31570
32266
  v1: v1Contract
31571
32267
  });
31572
32268
 
@@ -31615,7 +32311,7 @@ var ComplianceRegimeValues = [
31615
32311
  var ComplianceRegime = external_exports.enum(ComplianceRegimeValues);
31616
32312
 
31617
32313
  // ../../packages/api-contracts/src/contracts/v1/agent-session-internal.ts
31618
- var c42 = initContract();
32314
+ var c41 = initContract();
31619
32315
  var AgentSessionInternalParamsSchema = external_exports.object({
31620
32316
  agentSessionId: external_exports.uuid()
31621
32317
  });
@@ -31729,8 +32425,13 @@ var RecordCandidateVulnRequestSchema = external_exports.object({
31729
32425
  /** Idempotency key, stable across POST retries of one tool invocation. */
31730
32426
  sourceKey: external_exports.string().min(1).max(256),
31731
32427
  candidate: external_exports.object({
31732
- title: external_exports.string().min(1).max(200),
31733
- description: external_exports.string().min(1).max(5e3),
32428
+ // Kept in lockstep with the record_candidate_vuln tool schema in
32429
+ // apps/agent-session-host/src/recording.ts — a tool cap looser than this
32430
+ // one turns a model-visible limit into an opaque API rejection. title 250 /
32431
+ // description 6000 were raised from 200/5000, which observed usage had
32432
+ // saturated (median description 4154 of 5000).
32433
+ title: external_exports.string().min(1).max(250),
32434
+ description: external_exports.string().min(1).max(6e3),
31734
32435
  attackChain: external_exports.string().min(1).max(5e3),
31735
32436
  exploitTechnique: external_exports.string().min(1).max(5e3),
31736
32437
  prerequisiteAccess: external_exports.string().min(1).max(5e3).optional(),
@@ -31773,7 +32474,7 @@ var RecordProductContextResponseSchema = external_exports.object({
31773
32474
  disputed: external_exports.number().int().nonnegative(),
31774
32475
  rejected: external_exports.number().int().nonnegative()
31775
32476
  });
31776
- var ThreatModelStatement = external_exports.string().min(1).max(8e3);
32477
+ var ThreatModelStatement = external_exports.string().min(1).max(2e3);
31777
32478
  var RepoScopedThreatItem = external_exports.object({
31778
32479
  repoAlias: external_exports.string().min(1).max(200),
31779
32480
  statement: ThreatModelStatement
@@ -31924,17 +32625,71 @@ var RecordValidationResultRequestSchema = external_exports.object({
31924
32625
  * customerFacing labels. The api stores them on the `PentestExploitArtifact` row (caption →
31925
32626
  * `description`) and links them to the promoted finding.
31926
32627
  */
31927
- evidence: external_exports.array(RecordValidationEvidenceItemSchema).max(32).optional()
32628
+ evidence: external_exports.array(RecordValidationEvidenceItemSchema).max(32).optional(),
32629
+ /**
32630
+ * PROVEN-only. The EXISTING finding this proof belongs to — the same root-cause
32631
+ * weakness, already reported by an earlier session. Set it INSTEAD of promoting:
32632
+ * the covered candidate(s) attach to that finding and no second finding is created.
32633
+ *
32634
+ * When set, `prose` is that finding's COMPLETE prose with this investigation's
32635
+ * addition folded in (read it with get_finding first), not a fragment — the api
32636
+ * writes the prose columns verbatim. Scoring fields (cvssVector / cweIds / category /
32637
+ * owaspCategory / severity) are REJECTED on a bind rather than silently dropped; a
32638
+ * second proof of a known weakness must not re-score a finding staff already
32639
+ * triaged. `repositories` IS applied, additively.
32640
+ */
32641
+ bindToPentestFindingId: external_exports.uuid().optional(),
32642
+ /**
32643
+ * The bound finding's `updatedAt` as returned by get_finding. Optimistic lock: a
32644
+ * staff edit landing between the read and this call returns BIND_STALE_FINDING
32645
+ * instead of silently clobbering their text. Required whenever
32646
+ * `bindToPentestFindingId` is set AND `prose` is supplied.
32647
+ */
32648
+ bindExpectedUpdatedAt: external_exports.coerce.date().optional()
31928
32649
  }).refine((body) => (body.hypothesisIds?.length ?? 0) > 0 || body.hypothesisId !== void 0, {
31929
32650
  message: "record_validation_result requires hypothesisIds (or the legacy hypothesisId)",
31930
32651
  path: ["hypothesisIds"]
31931
- });
32652
+ }).refine(
32653
+ (body) => body.bindToPentestFindingId === void 0 || body.prose == null || body.bindExpectedUpdatedAt !== void 0,
32654
+ {
32655
+ message: "bindExpectedUpdatedAt is required when binding with prose (pass the finding updatedAt from get_finding)",
32656
+ path: ["bindExpectedUpdatedAt"]
32657
+ }
32658
+ );
31932
32659
  var RecordValidationResultResponseSchema = external_exports.object({
31933
32660
  attemptId: external_exports.uuid(),
31934
32661
  verdict: ValidationVerdict,
31935
32662
  /** True when a PentestFinding was created from this PROVEN result. */
31936
32663
  promoted: external_exports.boolean(),
31937
- findingDisplayId: external_exports.number().int().positive().optional(),
32664
+ /**
32665
+ * The promoted finding's customer-facing number, or NULL when it has none yet — a
32666
+ * freshly promoted finding is UNPUBLISHED, and publishing is what assigns the number.
32667
+ * `promoted: true` with `findingDisplayId: null` means "finding created, not yet
32668
+ * numbered".
32669
+ */
32670
+ findingDisplayId: external_exports.number().int().positive().nullish(),
32671
+ /**
32672
+ * The promoted finding's durable id, present whenever `promoted` is true. This is the
32673
+ * handle the agent acts on — and once numbering moves to publish it is the ONLY handle
32674
+ * a freshly promoted finding has, since `findingDisplayId` is null until then.
32675
+ */
32676
+ pentestFindingId: external_exports.uuid().optional(),
32677
+ /**
32678
+ * True when the verdict attached to an EXISTING finding (`bindToPentestFindingId`)
32679
+ * instead of promoting a new one. `promoted` stays false in that case, while
32680
+ * `pentestFindingId` carries the bound finding — so the agent still holds the handle
32681
+ * for the finding it just acted on.
32682
+ */
32683
+ bound: external_exports.boolean().optional(),
32684
+ /**
32685
+ * How the bind's addition landed. REPORT_PROSE = merged into the finding's report prose
32686
+ * and its repository links (it was unpublished). FINDING_NOTE = filed as a staff note
32687
+ * instead, because the finding is PUBLISHED: nothing customer-facing on it is
32688
+ * agent-editable, which covers the prose AND any cited repository locations. NONE =
32689
+ * there was nothing to apply — no prose and no citations, or a replay, whose original
32690
+ * call already applied whatever it carried. Present whenever `bound`.
32691
+ */
32692
+ deltaApplied: external_exports.enum(["REPORT_PROSE", "FINDING_NOTE", "NONE"]).optional(),
31938
32693
  /**
31939
32694
  * The candidate(s) the verdict was bound to (PR A) — each as {displayId, title} so the
31940
32695
  * agent can notice a mis-tag by comparing the echoed TITLE against what it actually
@@ -31999,6 +32754,55 @@ var AgentSessionLoginCodeResponseSchema = external_exports.object({
31999
32754
  value: external_exports.string().optional(),
32000
32755
  receivedAt: external_exports.coerce.date().optional()
32001
32756
  });
32757
+ var AgentSessionListInboxMessagesRequestSchema = external_exports.object({
32758
+ runVersion: external_exports.number().int().nonnegative(),
32759
+ /** Which account's inbox (by `Credential.label`); omit if exactly one is readable. */
32760
+ credentialLabel: external_exports.string().min(1).max(200).optional(),
32761
+ /** Max messages, newest first. Clamped server-side. */
32762
+ limit: external_exports.number().int().positive().max(50).optional()
32763
+ });
32764
+ var InboxMessageSummarySchema = external_exports.object({
32765
+ messageId: external_exports.string().min(1).max(500),
32766
+ from: external_exports.string().max(500),
32767
+ subject: external_exports.string().max(1e3).nullable(),
32768
+ /** Provider-generated short body preview. */
32769
+ preview: external_exports.string().max(2e3).nullable(),
32770
+ receivedAt: external_exports.coerce.date(),
32771
+ unread: external_exports.boolean()
32772
+ });
32773
+ var AgentSessionListInboxMessagesResponseSchema = external_exports.object({
32774
+ /** The account whose inbox was read, echoed so the agent can pass it to get_email. */
32775
+ credentialLabel: external_exports.string().max(200),
32776
+ messages: external_exports.array(InboxMessageSummarySchema)
32777
+ });
32778
+ var AgentSessionGetInboxMessageRequestSchema = external_exports.object({
32779
+ runVersion: external_exports.number().int().nonnegative(),
32780
+ /** Message id from a `list_emails` result. */
32781
+ messageId: external_exports.string().min(1).max(500),
32782
+ credentialLabel: external_exports.string().min(1).max(200).optional()
32783
+ });
32784
+ var AgentSessionGetInboxMessageResponseSchema = external_exports.object({
32785
+ credentialLabel: external_exports.string().max(200),
32786
+ message: InboxMessageSummarySchema.extend({
32787
+ /**
32788
+ * The body as plain text, prompt-injection-neutralized and length-capped
32789
+ * ({@link INBOX_MESSAGE_BODY_MAX_CHARS}). Deliberately NOT run through the
32790
+ * credential redactor — that would rewrite the `?token=…` in a magic link,
32791
+ * which is the artifact the agent needs.
32792
+ */
32793
+ body: external_exports.string(),
32794
+ /** True when the body was truncated at the cap. */
32795
+ truncated: external_exports.boolean()
32796
+ }),
32797
+ /**
32798
+ * http(s) links extracted from the message in priority order (hrefs first,
32799
+ * then inline text URLs), pre-computed so the agent doesn't have to parse the
32800
+ * body. Same extraction and same URL-safety guards the login flow used.
32801
+ */
32802
+ links: external_exports.array(external_exports.string()),
32803
+ /** Detected one-time code, if the message carried one. */
32804
+ otp: external_exports.string().nullable()
32805
+ });
32002
32806
  var RenewCloudCredentialsRequestSchema = external_exports.object({
32003
32807
  runVersion: external_exports.number().int().nonnegative()
32004
32808
  });
@@ -32014,6 +32818,42 @@ var RenewCloudCredentialsResponseSchema = external_exports.object({
32014
32818
  accessToken: external_exports.string().min(1)
32015
32819
  }).nullable()
32016
32820
  });
32821
+ var ConvexReconRequestSchema = external_exports.object({
32822
+ runVersion: external_exports.number().int().nonnegative()
32823
+ });
32824
+ var ConvexReconInventorySchema = external_exports.discriminatedUnion("status", [
32825
+ /**
32826
+ * Convex's `apiSpec` payload, forwarded verbatim: per function the identifier,
32827
+ * type, visibility (public/internal) and argument validators, plus httpRoutes
32828
+ * and cronSpecs. Deliberately `unknown` — it describes the customer's own
32829
+ * deployment, not a Trace domain object, so pinning its shape would break when
32830
+ * Convex extends it.
32831
+ */
32832
+ external_exports.object({ status: external_exports.literal("ok"), functions: external_exports.unknown() }),
32833
+ /** Convex answered HTTP 200 with `status: "error"` — a function-level error. */
32834
+ external_exports.object({ status: external_exports.literal("error"), message: external_exports.string() }),
32835
+ /** Convex 401/403 — the grant is gone; the row was flipped ACCESS_DENIED. */
32836
+ external_exports.object({ status: external_exports.literal("access_denied") }),
32837
+ /** Convex 429/5xx/timeout, a decrypt failure, or the response budget spent. */
32838
+ external_exports.object({ status: external_exports.literal("unavailable") }),
32839
+ /** Over the per-deployment byte cap. NEVER truncated — a half inventory read as
32840
+ * complete is worse than an explicit gap. */
32841
+ external_exports.object({ status: external_exports.literal("too_large"), bytes: external_exports.number().int().nonnegative() })
32842
+ ]);
32843
+ var ConvexReconDeploymentSchema = external_exports.object({
32844
+ deploymentName: external_exports.string().min(1),
32845
+ /** Convex's DeploymentType verbatim (dev/prod/preview/custom) — never an enum. */
32846
+ deploymentType: external_exports.string().min(1),
32847
+ /** Region-qualified, from the stored row. NEVER synthesised from deploymentName. */
32848
+ convexCloudUrl: external_exports.string().min(1),
32849
+ /** Null when get_canonical_urls failed at connect; the host is not derivable. */
32850
+ convexSiteUrl: external_exports.string().min(1).nullable(),
32851
+ inventory: ConvexReconInventorySchema
32852
+ });
32853
+ var ConvexReconResponseSchema = external_exports.object({
32854
+ /** Empty when the org has no connected, key-bearing Convex deployment. */
32855
+ deployments: external_exports.array(ConvexReconDeploymentSchema)
32856
+ });
32017
32857
  var AgentSessionGetSessionStateRequestSchema = external_exports.object({
32018
32858
  runVersion: external_exports.number().int().nonnegative(),
32019
32859
  /** Which SESSION_STATE credential to fetch (from the non-secret env reference). */
@@ -32154,6 +32994,32 @@ var OperatorRecordFindingRetestRequestSchema = CreatePentestFindingRetestRequest
32154
32994
  });
32155
32995
  }
32156
32996
  });
32997
+ var OperatorRetestAckSchema = OperatorFindingReadSchema.extend({
32998
+ retestId: external_exports.uuid()
32999
+ });
33000
+ var OperatorEditFindingRetestRequestSchema = external_exports.object({
33001
+ runVersion: operatorRunVersion,
33002
+ reason: operatorReason,
33003
+ retestId: external_exports.uuid(),
33004
+ outcome: RetestOutcome.optional(),
33005
+ notes: external_exports.string().trim().min(1).max(1e4).optional(),
33006
+ verification: external_exports.string().min(1).max(8e3).optional()
33007
+ }).superRefine((v2, ctx) => {
33008
+ if (v2.outcome === void 0 && v2.notes === void 0) {
33009
+ ctx.addIssue({
33010
+ code: "custom",
33011
+ message: "provide outcome, notes, or both \u2014 an edit must change something",
33012
+ path: ["notes"]
33013
+ });
33014
+ }
33015
+ if (v2.verification !== void 0 && v2.outcome !== "FIXED") {
33016
+ ctx.addIssue({
33017
+ code: "custom",
33018
+ message: "verification report prose is only allowed when outcome is FIXED",
33019
+ path: ["verification"]
33020
+ });
33021
+ }
33022
+ });
32157
33023
  var OperatorMarkRemediationReportedRequestSchema = MarkPentestFindingRemediationReportedRequestSchema.extend({
32158
33024
  runVersion: operatorRunVersion,
32159
33025
  reason: operatorReason,
@@ -32300,7 +33166,7 @@ var ReportUpdateScopeRequestSchema = external_exports.object({
32300
33166
  expectedUpdatedAt: reportExpectedUpdatedAt,
32301
33167
  rows: external_exports.array(ScopeRowSchema).max(100)
32302
33168
  });
32303
- var agentSessionInternalContract = c42.router({
33169
+ var agentSessionInternalContract = c41.router({
32304
33170
  postEvents: {
32305
33171
  method: "POST",
32306
33172
  path: "/internal/v1/agent-sessions/:agentSessionId/events",
@@ -32457,6 +33323,10 @@ var agentSessionInternalContract = c42.router({
32457
33323
  200: ReattributeValidationResultResponseSchema,
32458
33324
  400: ErrorResponseSchema,
32459
33325
  401: ErrorResponseSchema,
33326
+ // The attempt or the target hypothesis does not exist in this pentest — in practice a
33327
+ // mistyped id in the agent's call. Declared because it is the likeliest failure here and
33328
+ // a reader who assumes it cannot 404 mis-diagnoses the retry loop it causes.
33329
+ 404: ErrorResponseSchema,
32460
33330
  // runVersion/status guard, stage mismatch, R-not-approved, R-already-promoted,
32461
33331
  // or attempt-not-in-run.
32462
33332
  409: ErrorResponseSchema,
@@ -32478,7 +33348,53 @@ var agentSessionInternalContract = c42.router({
32478
33348
  409: ErrorResponseSchema,
32479
33349
  // No MAGIC_AUTH credential on the pentest's environments.
32480
33350
  404: ErrorResponseSchema,
32481
- 500: ErrorResponseSchema
33351
+ 500: ErrorResponseSchema,
33352
+ // Mail provider unavailable — RETRYABLE, and distinct from 500 so the host
33353
+ // can tell "we could not look" from "this will never work".
33354
+ 503: ErrorResponseSchema
33355
+ }
33356
+ },
33357
+ // list_emails: newest-first metadata for the pentest's readable Trace-managed
33358
+ // inbox. Same resolution + ownership gate as login-code (org + pentest
33359
+ // environments + provider-side inbox tenancy); no bodies cross the wire here.
33360
+ listInboxMessages: {
33361
+ method: "POST",
33362
+ path: "/internal/v1/agent-sessions/:agentSessionId/inbox-messages",
33363
+ pathParams: AgentSessionInternalParamsSchema,
33364
+ body: AgentSessionListInboxMessagesRequestSchema,
33365
+ responses: {
33366
+ 200: AgentSessionListInboxMessagesResponseSchema,
33367
+ // Ambiguous credentialLabel (several readable inboxes, none named).
33368
+ 400: ErrorResponseSchema,
33369
+ 401: ErrorResponseSchema,
33370
+ 409: ErrorResponseSchema,
33371
+ // No readable Trace-managed inbox on the pentest's environments, or the
33372
+ // resolved address is not owned by this run's organization.
33373
+ 404: ErrorResponseSchema,
33374
+ 500: ErrorResponseSchema,
33375
+ // Mail provider unavailable — RETRYABLE, and distinct from 500 so the host
33376
+ // can tell "we could not look" from "this will never work".
33377
+ 503: ErrorResponseSchema
33378
+ }
33379
+ },
33380
+ // get_email: one message from that inbox, body included. The body is
33381
+ // third-party text — the api neutralizes and length-caps it before it can
33382
+ // reach the agent's context.
33383
+ getInboxMessage: {
33384
+ method: "POST",
33385
+ path: "/internal/v1/agent-sessions/:agentSessionId/inbox-message",
33386
+ pathParams: AgentSessionInternalParamsSchema,
33387
+ body: AgentSessionGetInboxMessageRequestSchema,
33388
+ responses: {
33389
+ 200: AgentSessionGetInboxMessageResponseSchema,
33390
+ 400: ErrorResponseSchema,
33391
+ 401: ErrorResponseSchema,
33392
+ 409: ErrorResponseSchema,
33393
+ // Unknown inbox (as above) OR the message id is not in that inbox.
33394
+ 404: ErrorResponseSchema,
33395
+ 500: ErrorResponseSchema,
33396
+ // Mail provider unavailable — RETRYABLE (see listInboxMessages).
33397
+ 503: ErrorResponseSchema
32482
33398
  }
32483
33399
  },
32484
33400
  // get_session_state (SESSION_STATE, spec §2/§3): api-mediated JIT fetch — the api
@@ -32518,6 +33434,26 @@ var agentSessionInternalContract = c42.router({
32518
33434
  500: ErrorResponseSchema
32519
33435
  }
32520
33436
  },
33437
+ // get_convex_deployments (Convex integration PR 3): the read-only broker. Every
33438
+ // connected Convex deployment for the run's OWN org, each with its function
33439
+ // inventory. No request identifier at all — see the schema block above. No 404:
33440
+ // there is no agent-supplied id to miss. No 503: a Convex outage is the
33441
+ // per-deployment `unavailable` status, so one bad deployment never fails the rest.
33442
+ convexRecon: {
33443
+ method: "POST",
33444
+ path: "/internal/v1/agent-sessions/:agentSessionId/convex/recon",
33445
+ pathParams: AgentSessionInternalParamsSchema,
33446
+ body: ConvexReconRequestSchema,
33447
+ responses: {
33448
+ 200: ConvexReconResponseSchema,
33449
+ 400: ErrorResponseSchema,
33450
+ 401: ErrorResponseSchema,
33451
+ // Non-credentialed session type — this run may never reach customer clouds.
33452
+ 403: ErrorResponseSchema,
33453
+ 409: ErrorResponseSchema,
33454
+ 500: ErrorResponseSchema
33455
+ }
33456
+ },
32521
33457
  // Operator reads (operator-session PR 1 §2b). All run-token authed; org+pentest
32522
33458
  // derived from the session row. Detail ops 404 when the id is not in this pentest.
32523
33459
  getPentestContext: {
@@ -32714,7 +33650,21 @@ var agentSessionInternalContract = c42.router({
32714
33650
  pathParams: AgentSessionInternalParamsSchema,
32715
33651
  body: OperatorRecordFindingRetestRequestSchema,
32716
33652
  responses: {
32717
- 200: OperatorFindingReadSchema,
33653
+ 200: OperatorRetestAckSchema,
33654
+ 400: ErrorResponseSchema,
33655
+ 401: ErrorResponseSchema,
33656
+ 404: ErrorResponseSchema,
33657
+ 409: ErrorResponseSchema,
33658
+ 500: ErrorResponseSchema
33659
+ }
33660
+ },
33661
+ editFindingRetest: {
33662
+ method: "POST",
33663
+ path: "/internal/v1/agent-sessions/:agentSessionId/operator/findings/retest/edit",
33664
+ pathParams: AgentSessionInternalParamsSchema,
33665
+ body: OperatorEditFindingRetestRequestSchema,
33666
+ responses: {
33667
+ 200: OperatorRetestAckSchema,
32718
33668
  400: ErrorResponseSchema,
32719
33669
  401: ErrorResponseSchema,
32720
33670
  404: ErrorResponseSchema,
@@ -32916,8 +33866,8 @@ var GitHubWebhookResponseSchema = external_exports.object({
32916
33866
  });
32917
33867
 
32918
33868
  // ../../packages/api-contracts/src/contracts/webhooks/github.ts
32919
- var c43 = initContract();
32920
- var githubWebhookContract = c43.router(
33869
+ var c42 = initContract();
33870
+ var githubWebhookContract = c42.router(
32921
33871
  {
32922
33872
  handleWebhook: {
32923
33873
  method: "POST",
@@ -32935,7 +33885,7 @@ var githubWebhookContract = c43.router(
32935
33885
  pathPrefix: "/webhooks"
32936
33886
  }
32937
33887
  );
32938
- var githubReadonlyWebhookContract = c43.router(
33888
+ var githubReadonlyWebhookContract = c42.router(
32939
33889
  {
32940
33890
  handleWebhook: {
32941
33891
  method: "POST",
@@ -32955,9 +33905,9 @@ var githubReadonlyWebhookContract = c43.router(
32955
33905
  );
32956
33906
 
32957
33907
  // ../../packages/api-contracts/src/contracts/webhooks/linear.ts
32958
- var c44 = initContract();
33908
+ var c43 = initContract();
32959
33909
  var LinearWebhookResponseSchema = external_exports.object({ ok: external_exports.boolean() });
32960
- var linearWebhookContract = c44.router(
33910
+ var linearWebhookContract = c43.router(
32961
33911
  {
32962
33912
  handleWebhook: {
32963
33913
  method: "POST",
@@ -32977,7 +33927,7 @@ var linearWebhookContract = c44.router(
32977
33927
  );
32978
33928
 
32979
33929
  // ../../packages/api-contracts/src/contracts/forge/bitbucket.ts
32980
- var c45 = initContract();
33930
+ var c44 = initContract();
32981
33931
  var InstallEventSchema = external_exports.object({
32982
33932
  eventType: external_exports.enum(["avi:forge:installed:app", "avi:forge:upgraded:app"]),
32983
33933
  context: external_exports.object({ cloudId: external_exports.string().min(1) }).loose(),
@@ -33006,7 +33956,7 @@ var ForgePairingCodeResponseSchema = external_exports.discriminatedUnion("state"
33006
33956
  expiresInSeconds: external_exports.number().int()
33007
33957
  })
33008
33958
  ]);
33009
- var forgeBitbucketContract = c45.router(
33959
+ var forgeBitbucketContract = c44.router(
33010
33960
  {
33011
33961
  lifecycle: {
33012
33962
  method: "POST",
@@ -33053,8 +34003,8 @@ var HealthResponseSchema = external_exports.object({
33053
34003
  });
33054
34004
 
33055
34005
  // ../../packages/api-contracts/src/contracts/health.ts
33056
- var c46 = initContract();
33057
- var healthContract = c46.router({
34006
+ var c45 = initContract();
34007
+ var healthContract = c45.router({
33058
34008
  getHealth: {
33059
34009
  method: "GET",
33060
34010
  path: "/health",
@@ -33066,8 +34016,8 @@ var healthContract = c46.router({
33066
34016
  });
33067
34017
 
33068
34018
  // ../../packages/api-contracts/src/contracts/v1/vulnerability-share.ts
33069
- var c47 = initContract();
33070
- var vulnerabilityShareContract = c47.router(
34019
+ var c46 = initContract();
34020
+ var vulnerabilityShareContract = c46.router(
33071
34021
  {
33072
34022
  resolveShareLink: {
33073
34023
  method: "GET",
@@ -33154,8 +34104,8 @@ var UpdatePrVulnerabilityRequestSchema = external_exports.object({
33154
34104
  });
33155
34105
 
33156
34106
  // ../../packages/api-contracts/src/contracts/v1/pr-vulnerability.ts
33157
- var c48 = initContract();
33158
- var prVulnerabilityContract = c48.router(
34107
+ var c47 = initContract();
34108
+ var prVulnerabilityContract = c47.router(
33159
34109
  {
33160
34110
  listPrVulnerabilities: {
33161
34111
  method: "GET",
@@ -35541,6 +36491,7 @@ var APPLICATION_TYPE_BY_INPUT = {
35541
36491
  web: "WEB",
35542
36492
  cli: "CLI",
35543
36493
  mobile: "MOBILE",
36494
+ desktop: "DESKTOP",
35544
36495
  "api-service": "API_SERVICE",
35545
36496
  api_service: "API_SERVICE"
35546
36497
  };
@@ -35707,12 +36658,12 @@ function buildApplicationsTable(apps) {
35707
36658
  repos: String(a.repositories.length),
35708
36659
  url: a.applicationUrl ? sanitizeDisplay(a.applicationUrl) : "\u2014"
35709
36660
  }));
35710
- const idWidth = Math.max(2, ...cells.map((c50) => c50.id.length));
35711
- const nameWidth = Math.max(4, ...cells.map((c50) => c50.name.length));
35712
- const typeWidth = Math.max(4, ...cells.map((c50) => c50.type.length));
36661
+ const idWidth = Math.max(2, ...cells.map((c49) => c49.id.length));
36662
+ const nameWidth = Math.max(4, ...cells.map((c49) => c49.name.length));
36663
+ const typeWidth = Math.max(4, ...cells.map((c49) => c49.type.length));
35713
36664
  const header = `${"ID".padEnd(idWidth)} ${"NAME".padEnd(nameWidth)} ${"TYPE".padEnd(typeWidth)} REPOS URL`;
35714
36665
  const rows = cells.map(
35715
- (c50) => `${c50.id.padEnd(idWidth)} ${c50.name.padEnd(nameWidth)} ${c50.type.padEnd(typeWidth)} ${c50.repos.padStart(5)} ${c50.url}`
36666
+ (c49) => `${c49.id.padEnd(idWidth)} ${c49.name.padEnd(nameWidth)} ${c49.type.padEnd(typeWidth)} ${c49.repos.padStart(5)} ${c49.url}`
35716
36667
  );
35717
36668
  return { header, rows };
35718
36669
  }
@@ -35732,12 +36683,12 @@ function buildApplicationRepositoriesTable(links) {
35732
36683
  path: l.repositoryPath ? sanitizeDisplay(l.repositoryPath) : "\u2014",
35733
36684
  status: formatStatus(l.repositoryStatus)
35734
36685
  }));
35735
- const nameWidth = Math.max(10, ...cells.map((c50) => c50.name.length));
35736
- const roleWidth = Math.max(4, ...cells.map((c50) => c50.role.length));
35737
- const pathWidth = Math.max(4, ...cells.map((c50) => c50.path.length));
36686
+ const nameWidth = Math.max(10, ...cells.map((c49) => c49.name.length));
36687
+ const roleWidth = Math.max(4, ...cells.map((c49) => c49.role.length));
36688
+ const pathWidth = Math.max(4, ...cells.map((c49) => c49.path.length));
35738
36689
  const header = `${"REPOSITORY".padEnd(nameWidth)} SCAN ${"ROLE".padEnd(roleWidth)} ${"PATH".padEnd(pathWidth)} STATUS`;
35739
36690
  const rows = cells.map(
35740
- (c50) => `${c50.name.padEnd(nameWidth)} ${c50.scan.padEnd(4)} ${c50.role.padEnd(roleWidth)} ${c50.path.padEnd(pathWidth)} ${c50.status}`
36691
+ (c49) => `${c49.name.padEnd(nameWidth)} ${c49.scan.padEnd(4)} ${c49.role.padEnd(roleWidth)} ${c49.path.padEnd(pathWidth)} ${c49.status}`
35741
36692
  );
35742
36693
  return { header, rows };
35743
36694
  }
@@ -35999,7 +36950,7 @@ async function runDelete(applicationId, options) {
35999
36950
  console.log(colorSuccess(`Deleted application ${applicationId}`));
36000
36951
  }
36001
36952
  function createCreateCommand() {
36002
- return new Command("create").description("Create a new application").argument("<name>", "Application name").option("--type <type>", "Application type: web, cli, mobile, api-service", "web").option("--url <url>", "Application URL (required for web/api-service)").option("--repo <id-or-url>", "Link a repository at creation (UUID, URL, or owner/repo)").option("--repo-path <path>", "Path within the linked repository").option("--help-center-url <url>", "Help center URL").option("--docs-url <url>", "Documentation URL").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
36953
+ return new Command("create").description("Create a new application").argument("<name>", "Application name").option("--type <type>", "Application type: web, cli, mobile, desktop, api-service", "web").option("--url <url>", "Application URL (required for web/api-service)").option("--repo <id-or-url>", "Link a repository at creation (UUID, URL, or owner/repo)").option("--repo-path <path>", "Path within the linked repository").option("--help-center-url <url>", "Help center URL").option("--docs-url <url>", "Documentation URL").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
36003
36954
  "after",
36004
36955
  `
36005
36956
  Examples:
@@ -36418,6 +37369,21 @@ function d(r, t, s) {
36418
37369
  const e2 = r + t, i = Math.max(s.length - 1, 0), n = e2 < 0 ? i : e2 > i ? 0 : e2;
36419
37370
  return s[n].disabled ? d(n, t < 0 ? -1 : 1, s) : n;
36420
37371
  }
37372
+ function I(r, t, s, e2) {
37373
+ const i = e2.split(`
37374
+ `);
37375
+ let n = 0, o = r;
37376
+ for (const a of i) {
37377
+ if (o <= a.length) break;
37378
+ o -= a.length + 1, n++;
37379
+ }
37380
+ for (n = Math.max(0, Math.min(i.length - 1, n + s)), o = Math.min(o, i[n].length) + t; o < 0 && n > 0; ) n--, o += i[n].length + 1;
37381
+ for (; o > i[n].length && n < i.length - 1; ) o -= i[n].length + 1, n++;
37382
+ o = Math.max(0, Math.min(i[n].length, o));
37383
+ let u = 0;
37384
+ for (let a = 0; a < n; a++) u += i[a].length + 1;
37385
+ return u + o;
37386
+ }
36421
37387
  var G = ["up", "down", "left", "right", "space", "enter", "cancel"];
36422
37388
  var K = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
36423
37389
  var h = { actions: new Set(G), aliases: /* @__PURE__ */ new Map([["k", "up"], ["j", "down"], ["h", "left"], ["l", "right"], ["", "cancel"], ["escape", "cancel"]]), messages: { cancel: "Canceled", error: "Something went wrong" }, withGuide: true, date: { monthNames: [...K], messages: { required: "Please enter a valid date", invalidMonth: "There are only 12 months in a year", invalidDay: (r, t) => `There are only ${r} days in ${t}`, afterMin: (r) => `Date must be on or after ${r.toISOString().slice(0, 10)}`, beforeMax: (r) => `Date must be on or before ${r.toISOString().slice(0, 10)}` } } };
@@ -36579,6 +37545,145 @@ var p = class {
36579
37545
  }
36580
37546
  }
36581
37547
  };
37548
+ var X = class extends p {
37549
+ get cursor() {
37550
+ return this.value ? 0 : 1;
37551
+ }
37552
+ get _value() {
37553
+ return this.cursor === 0;
37554
+ }
37555
+ constructor(t) {
37556
+ super(t, false), this.value = !!t.initialValue, this.on("userInput", () => {
37557
+ this.value = this._value;
37558
+ }), this.on("confirm", (s) => {
37559
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s, this.state = "submit", this.close();
37560
+ }), this.on("cursor", () => {
37561
+ this.value = !this.value;
37562
+ });
37563
+ }
37564
+ };
37565
+ var rt = class extends p {
37566
+ #s = false;
37567
+ #r;
37568
+ focused = "editor";
37569
+ get userInputWithCursor() {
37570
+ if (this.state === "submit") return this.userInput;
37571
+ const t = this.userInput;
37572
+ if (this.cursor >= t.length) return `${t}\u2588`;
37573
+ const s = t.slice(0, this.cursor), e2 = t[this.cursor], i = t.slice(this.cursor + 1);
37574
+ return e2 === `
37575
+ ` ? `${s}\u2588
37576
+ ${i}` : `${s}${v("inverse", e2)}${i}`;
37577
+ }
37578
+ get cursor() {
37579
+ return this._cursor;
37580
+ }
37581
+ #t(t) {
37582
+ if (this.userInput.length === 0) {
37583
+ this._setUserInput(t);
37584
+ return;
37585
+ }
37586
+ this._setUserInput(this.userInput.slice(0, this.cursor) + t + this.userInput.slice(this.cursor));
37587
+ }
37588
+ #n(t) {
37589
+ const s = this.value ?? "";
37590
+ switch (t) {
37591
+ case "up":
37592
+ this._cursor = I(this._cursor, 0, -1, s);
37593
+ return;
37594
+ case "down":
37595
+ this._cursor = I(this._cursor, 0, 1, s);
37596
+ return;
37597
+ case "left":
37598
+ this._cursor = I(this._cursor, -1, 0, s);
37599
+ return;
37600
+ case "right":
37601
+ this._cursor = I(this._cursor, 1, 0, s);
37602
+ return;
37603
+ }
37604
+ }
37605
+ _shouldSubmit(t, s) {
37606
+ if (this.#r) return this.focused === "submit" ? true : (this.#t(`
37607
+ `), this._cursor++, false);
37608
+ const e2 = this.#s;
37609
+ return this.#s = true, e2 ? (this.userInput[this.cursor - 1] === `
37610
+ ` && (this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--), true) : (this.#t(`
37611
+ `), this._cursor++, false);
37612
+ }
37613
+ constructor(t) {
37614
+ super(t, false), this.#r = t.showSubmit ?? false, this.on("key", (s, e2) => {
37615
+ if (e2?.name && h.actions.has(e2.name)) {
37616
+ this.#n(e2.name);
37617
+ return;
37618
+ }
37619
+ if (s === " " && this.#r) {
37620
+ this.focused = this.focused === "editor" ? "submit" : "editor";
37621
+ return;
37622
+ }
37623
+ if (e2?.name !== "return") {
37624
+ if (this.#s = false, e2?.name === "backspace" && this.cursor > 0) {
37625
+ this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--;
37626
+ return;
37627
+ }
37628
+ if (e2?.name === "delete" && this.cursor < this.userInput.length) {
37629
+ this._setUserInput(this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1));
37630
+ return;
37631
+ }
37632
+ s && (this.#r && this.focused === "submit" && (this.focused = "editor"), this.#t(s ?? ""), this._cursor++);
37633
+ }
37634
+ }), this.on("userInput", (s) => {
37635
+ this._setValue(s);
37636
+ }), this.on("finalize", () => {
37637
+ this.value || (this.value = t.defaultValue), this.value === void 0 && (this.value = "");
37638
+ });
37639
+ }
37640
+ };
37641
+ var nt = class extends p {
37642
+ options;
37643
+ cursor = 0;
37644
+ get _value() {
37645
+ return this.options[this.cursor].value;
37646
+ }
37647
+ get _enabledOptions() {
37648
+ return this.options.filter((t) => t.disabled !== true);
37649
+ }
37650
+ toggleAll() {
37651
+ const t = this._enabledOptions, s = this.value !== void 0 && this.value.length === t.length;
37652
+ this.value = s ? [] : t.map((e2) => e2.value);
37653
+ }
37654
+ toggleInvert() {
37655
+ const t = this.value;
37656
+ if (!t) return;
37657
+ const s = this._enabledOptions.filter((e2) => !t.includes(e2.value));
37658
+ this.value = s.map((e2) => e2.value);
37659
+ }
37660
+ toggleValue() {
37661
+ this.value === void 0 && (this.value = []);
37662
+ const t = this.value.includes(this._value);
37663
+ this.value = t ? this.value.filter((s) => s !== this._value) : [...this.value, this._value];
37664
+ }
37665
+ constructor(t) {
37666
+ super(t, false), this.options = t.options, this.value = [...t.initialValues ?? []];
37667
+ const s = Math.max(this.options.findIndex(({ value: e2 }) => e2 === t.cursorAt), 0);
37668
+ this.cursor = this.options[s].disabled ? d(s, 1, this.options) : s, this.on("key", (e2) => {
37669
+ e2 === "a" && this.toggleAll(), e2 === "i" && this.toggleInvert();
37670
+ }), this.on("cursor", (e2) => {
37671
+ switch (e2) {
37672
+ case "left":
37673
+ case "up":
37674
+ this.cursor = d(this.cursor, -1, this.options);
37675
+ break;
37676
+ case "down":
37677
+ case "right":
37678
+ this.cursor = d(this.cursor, 1, this.options);
37679
+ break;
37680
+ case "space":
37681
+ this.toggleValue();
37682
+ break;
37683
+ }
37684
+ });
37685
+ }
37686
+ };
36582
37687
  var ot = class extends p {
36583
37688
  _mask = "\u2022";
36584
37689
  get cursor() {
@@ -36695,14 +37800,14 @@ var yt = (t) => {
36695
37800
  };
36696
37801
  var ee = (t, r, s, i, u) => {
36697
37802
  let n = r, o = 0;
36698
- for (let c50 = s; c50 < i; c50++) {
36699
- const a = t[c50];
37803
+ for (let c49 = s; c49 < i; c49++) {
37804
+ const a = t[c49];
36700
37805
  if (n = n - a.length, o++, n <= u) break;
36701
37806
  }
36702
37807
  return { lineCount: n, removals: o };
36703
37808
  };
36704
37809
  var Y2 = ({ cursor: t, options: r, style: s, output: i = process.stdout, maxItems: u = Number.POSITIVE_INFINITY, columnPadding: n = 0, rowPadding: o = 4 }) => {
36705
- const c50 = A(i) - n, a = L(i), l = e("dim", "..."), d2 = Math.max(a - o, 0), y = Math.max(Math.min(u, d2), 5);
37810
+ const c49 = A(i) - n, a = L(i), l = e("dim", "..."), d2 = Math.max(a - o, 0), y = Math.max(Math.min(u, d2), 5);
36706
37811
  let p2 = 0;
36707
37812
  t >= y - 3 && (p2 = Math.max(Math.min(t - y + 3, r.length - y), 0));
36708
37813
  let m = y < r.length && p2 > 0, g = y < r.length && p2 + y < r.length;
@@ -36711,7 +37816,7 @@ var Y2 = ({ cursor: t, options: r, style: s, output: i = process.stdout, maxItem
36711
37816
  m && f++, g && f++;
36712
37817
  const v2 = p2 + (m ? 1 : 0), T = S2 - (g ? 1 : 0);
36713
37818
  for (let b2 = v2; b2 < T; b2++) {
36714
- const G2 = wrapAnsi(s(r[b2], b2 === t), c50, { hard: true, trim: false }).split(`
37819
+ const G2 = wrapAnsi(s(r[b2], b2 === t), c49, { hard: true, trim: false }).split(`
36715
37820
  `);
36716
37821
  h2.push(G2), f += G2.length;
36717
37822
  }
@@ -36725,15 +37830,129 @@ var Y2 = ({ cursor: t, options: r, style: s, output: i = process.stdout, maxItem
36725
37830
  for (const b2 of h2) for (const G2 of b2) C2.push(G2);
36726
37831
  return g && C2.push(l), C2;
36727
37832
  };
37833
+ var ue = (t) => {
37834
+ const r = t.active ?? "Yes", s = t.inactive ?? "No";
37835
+ return new X({ active: r, inactive: s, signal: t.signal, input: t.input, output: t.output, initialValue: t.initialValue ?? true, render() {
37836
+ const i = t.withGuide ?? h.withGuide, u = `${M(this.state)} `, n = i ? `${e("gray", $)} ` : "", o = W(t.output, t.message, n, u), c49 = `${i ? `${e("gray", $)}
37837
+ ` : ""}${o}
37838
+ `, a = this.value ? r : s;
37839
+ switch (this.state) {
37840
+ case "submit": {
37841
+ const l = i ? `${e("gray", $)} ` : "";
37842
+ return `${c49}${l}${e("dim", a)}`;
37843
+ }
37844
+ case "cancel": {
37845
+ const l = i ? `${e("gray", $)} ` : "";
37846
+ return `${c49}${l}${e(["strikethrough", "dim"], a)}${i ? `
37847
+ ${e("gray", $)}` : ""}`;
37848
+ }
37849
+ default: {
37850
+ const l = i ? `${e("cyan", $)} ` : "", d2 = i ? e("cyan", E2) : "";
37851
+ return `${c49}${l}${this.value ? `${e("green", z4)} ${r}` : `${e("dim", H)} ${e("dim", r)}`}${t.vertical ? i ? `
37852
+ ${e("cyan", $)} ` : `
37853
+ ` : ` ${e("dim", "/")} `}${this.value ? `${e("dim", H)} ${e("dim", s)}` : `${e("green", z4)} ${s}`}
37854
+ ${d2}
37855
+ `;
37856
+ }
37857
+ }
37858
+ } }).prompt();
37859
+ };
37860
+ var me = (t = "", r) => {
37861
+ const s = r?.output ?? process.stdout, i = r?.withGuide ?? h.withGuide ? `${e("gray", E2)} ` : "";
37862
+ s.write(`${i}${e("red", t)}
37863
+
37864
+ `);
37865
+ };
37866
+ var fe = (t) => new rt({ validate: t.validate, placeholder: t.placeholder, defaultValue: t.defaultValue, initialValue: t.initialValue, showSubmit: t.showSubmit, output: t.output, signal: t.signal, input: t.input, render() {
37867
+ const r = t?.withGuide ?? h.withGuide, s = `${`${r ? `${e("gray", $)}
37868
+ ` : ""}${M(this.state)} `}${t.message}
37869
+ `, i = t.placeholder ? e("inverse", t.placeholder[0]) + e("dim", t.placeholder.slice(1)) : e(["inverse", "hidden"], "_"), u = this.userInput ? this.userInputWithCursor : i, n = this.value ?? "", o = t.showSubmit ? `
37870
+ ${e(this.focused === "submit" ? "cyan" : "dim", "[ submit ]")}` : "";
37871
+ switch (this.state) {
37872
+ case "error": {
37873
+ const c49 = `${e("yellow", $)} `, a = r ? W(t.output, u, c49, void 0) : u, l = e("yellow", E2);
37874
+ return `${s}${a}
37875
+ ${l} ${e("yellow", this.error)}${o}
37876
+ `;
37877
+ }
37878
+ case "submit": {
37879
+ const c49 = `${e("gray", $)} `, a = r ? W(t.output, n, c49, void 0, (l) => e("dim", l)) : n ? e("dim", n) : "";
37880
+ return `${s}${a}`;
37881
+ }
37882
+ case "cancel": {
37883
+ const c49 = `${e("gray", $)} `, a = r ? W(t.output, n, c49, void 0, (l) => e(["strikethrough", "dim"], l)) : n ? e(["strikethrough", "dim"], n) : "";
37884
+ return `${s}${a}`;
37885
+ }
37886
+ default: {
37887
+ const c49 = r ? `${e("cyan", $)} ` : "", a = r ? e("cyan", E2) : "", l = r ? W(t.output, u, c49) : u;
37888
+ return `${s}${l}
37889
+ ${a}${o}
37890
+ `;
37891
+ }
37892
+ }
37893
+ } }).prompt();
37894
+ var Q2 = (t, r) => t.split(`
37895
+ `).map((s) => r(s)).join(`
37896
+ `);
37897
+ var ve = (t) => {
37898
+ const r = (i, u) => {
37899
+ const n = i.label ?? String(i.value);
37900
+ return u === "disabled" ? `${e("gray", J)} ${Q2(n, (o) => e(["strikethrough", "gray"], o))}${i.hint ? ` ${e("dim", `(${i.hint ?? "disabled"})`)}` : ""}` : u === "active" ? `${e("cyan", et2)} ${n}${i.hint ? ` ${e("dim", `(${i.hint})`)}` : ""}` : u === "selected" ? `${e("green", U)} ${Q2(n, (o) => e("dim", o))}${i.hint ? ` ${e("dim", `(${i.hint})`)}` : ""}` : u === "cancelled" ? `${Q2(n, (o) => e(["strikethrough", "dim"], o))}` : u === "active-selected" ? `${e("green", U)} ${n}${i.hint ? ` ${e("dim", `(${i.hint})`)}` : ""}` : u === "submitted" ? `${Q2(n, (o) => e("dim", o))}` : `${e("dim", J)} ${Q2(n, (o) => e("dim", o))}`;
37901
+ }, s = t.required ?? true;
37902
+ return new nt({ options: t.options, signal: t.signal, input: t.input, output: t.output, initialValues: t.initialValues, required: s, cursorAt: t.cursorAt, validate(i) {
37903
+ if (s && (i === void 0 || i.length === 0)) return `Please select at least one option.
37904
+ ${e("reset", e("dim", `Press ${e(["gray", "bgWhite", "inverse"], " space ")} to select, ${e("gray", e("bgWhite", e("inverse", " enter ")))} to submit`))}`;
37905
+ }, render() {
37906
+ const i = t.withGuide ?? h.withGuide, u = W(t.output, t.message, i ? `${yt(this.state)} ` : "", `${M(this.state)} `), n = `${i ? `${e("gray", $)}
37907
+ ` : ""}${u}
37908
+ `, o = this.value ?? [], c49 = (a, l) => {
37909
+ if (a.disabled) return r(a, "disabled");
37910
+ const d2 = o.includes(a.value);
37911
+ return l && d2 ? r(a, "active-selected") : d2 ? r(a, "selected") : r(a, l ? "active" : "inactive");
37912
+ };
37913
+ switch (this.state) {
37914
+ case "submit": {
37915
+ const a = this.options.filter(({ value: d2 }) => o.includes(d2)).map((d2) => r(d2, "submitted")).join(e("dim", ", ")) || e("dim", "none"), l = W(t.output, a, i ? `${e("gray", $)} ` : "");
37916
+ return `${n}${l}`;
37917
+ }
37918
+ case "cancel": {
37919
+ const a = this.options.filter(({ value: d2 }) => o.includes(d2)).map((d2) => r(d2, "cancelled")).join(e("dim", ", "));
37920
+ if (a.trim() === "") return `${n}${e("gray", $)}`;
37921
+ const l = W(t.output, a, i ? `${e("gray", $)} ` : "");
37922
+ return `${n}${l}${i ? `
37923
+ ${e("gray", $)}` : ""}`;
37924
+ }
37925
+ case "error": {
37926
+ const a = i ? `${e("yellow", $)} ` : "", l = this.error.split(`
37927
+ `).map((p2, m) => m === 0 ? `${i ? `${e("yellow", E2)} ` : ""}${e("yellow", p2)}` : ` ${p2}`).join(`
37928
+ `), d2 = n.split(`
37929
+ `).length, y = l.split(`
37930
+ `).length + 1;
37931
+ return `${n}${a}${Y2({ output: t.output, options: this.options, cursor: this.cursor, maxItems: t.maxItems, columnPadding: a.length, rowPadding: d2 + y, style: c49 }).join(`
37932
+ ${a}`)}
37933
+ ${l}
37934
+ `;
37935
+ }
37936
+ default: {
37937
+ const a = i ? `${e("cyan", $)} ` : "", l = n.split(`
37938
+ `).length, d2 = i ? 2 : 1;
37939
+ return `${n}${a}${Y2({ output: t.output, options: this.options, cursor: this.cursor, maxItems: t.maxItems, columnPadding: a.length, rowPadding: l + d2, style: c49 }).join(`
37940
+ ${a}`)}
37941
+ ${i ? e("cyan", E2) : ""}
37942
+ `;
37943
+ }
37944
+ }
37945
+ } }).prompt();
37946
+ };
36728
37947
  var Ce = (t) => new ot({ validate: t.validate, mask: t.mask ?? Gt, signal: t.signal, input: t.input, output: t.output, render() {
36729
37948
  const r = t.withGuide ?? h.withGuide, s = `${r ? `${e("gray", $)}
36730
37949
  ` : ""}${M(this.state)} ${t.message}
36731
37950
  `, i = this.userInputWithCursor, u = this.masked;
36732
37951
  switch (this.state) {
36733
37952
  case "error": {
36734
- const n = r ? `${e("yellow", $)} ` : "", o = r ? `${e("yellow", E2)} ` : "", c50 = u ?? "";
37953
+ const n = r ? `${e("yellow", $)} ` : "", o = r ? `${e("yellow", E2)} ` : "", c49 = u ?? "";
36735
37954
  return t.clearOnError && this.clear(), `${s.trim()}
36736
- ${n}${c50}
37955
+ ${n}${c49}
36737
37956
  ${o}${e("yellow", this.error)}
36738
37957
  `;
36739
37958
  }
@@ -36781,19 +38000,19 @@ var Ee = (t) => {
36781
38000
  `;
36782
38001
  switch (this.state) {
36783
38002
  case "submit": {
36784
- const c50 = s ? `${e("gray", $)} ` : "", a = W(t.output, r(this.options[this.cursor], "selected"), c50);
38003
+ const c49 = s ? `${e("gray", $)} ` : "", a = W(t.output, r(this.options[this.cursor], "selected"), c49);
36785
38004
  return `${o}${a}`;
36786
38005
  }
36787
38006
  case "cancel": {
36788
- const c50 = s ? `${e("gray", $)} ` : "", a = W(t.output, r(this.options[this.cursor], "cancelled"), c50);
38007
+ const c49 = s ? `${e("gray", $)} ` : "", a = W(t.output, r(this.options[this.cursor], "cancelled"), c49);
36789
38008
  return `${o}${a}${s ? `
36790
38009
  ${e("gray", $)}` : ""}`;
36791
38010
  }
36792
38011
  default: {
36793
- const c50 = s ? `${e("cyan", $)} ` : "", a = s ? e("cyan", E2) : "", l = o.split(`
38012
+ const c49 = s ? `${e("cyan", $)} ` : "", a = s ? e("cyan", E2) : "", l = o.split(`
36794
38013
  `).length, d2 = s ? 2 : 1;
36795
- return `${o}${c50}${Y2({ output: t.output, cursor: this.cursor, options: this.options, maxItems: t.maxItems, columnPadding: c50.length, rowPadding: l + d2, style: (y, p2) => r(y, y.disabled ? "disabled" : p2 ? "active" : "inactive") }).join(`
36796
- ${c50}`)}
38014
+ return `${o}${c49}${Y2({ output: t.output, cursor: this.cursor, options: this.options, maxItems: t.maxItems, columnPadding: c49.length, rowPadding: l + d2, style: (y, p2) => r(y, y.disabled ? "disabled" : p2 ? "active" : "inactive") }).join(`
38015
+ ${c49}`)}
36797
38016
  ${a}
36798
38017
  `;
36799
38018
  }
@@ -37551,7 +38770,7 @@ function extractCommandTree(cmd) {
37551
38770
  description: o.description,
37552
38771
  takesValue: o.required || o.optional
37553
38772
  }));
37554
- const subcommands = cmd.commands.filter((c50) => !c50._hidden).map((c50) => extractCommandTree(c50));
38773
+ const subcommands = cmd.commands.filter((c49) => !c49._hidden).map((c49) => extractCommandTree(c49));
37555
38774
  return {
37556
38775
  name: cmd.name(),
37557
38776
  description: cmd.description(),
@@ -37617,7 +38836,7 @@ function generateBashCompletion(tree, binName) {
37617
38836
  }
37618
38837
  function collectBashCases(tree, prefix) {
37619
38838
  const cases = /* @__PURE__ */ new Map();
37620
- const subcommandNames = tree.subcommands.map((c50) => c50.name);
38839
+ const subcommandNames = tree.subcommands.map((c49) => c49.name);
37621
38840
  const optionNames = tree.options.flatMap((o) => o.short ? [o.long, o.short] : [o.long]);
37622
38841
  const extraOptions = prefix === "" ? ["--help", "--version"] : ["--help"];
37623
38842
  const allOptions = [.../* @__PURE__ */ new Set([...optionNames, ...extraOptions])];
@@ -38300,16 +39519,16 @@ function printCredentialsTsv(creds) {
38300
39519
  }
38301
39520
  }
38302
39521
  function buildCredentialsTable(creds) {
38303
- const cells = creds.map((c50) => ({
38304
- id: c50.credentialId,
38305
- label: sanitizeDisplay(c50.label),
38306
- type: c50.credentialType
39522
+ const cells = creds.map((c49) => ({
39523
+ id: c49.credentialId,
39524
+ label: sanitizeDisplay(c49.label),
39525
+ type: c49.credentialType
38307
39526
  }));
38308
- const idWidth = Math.max(2, ...cells.map((c50) => c50.id.length));
38309
- const labelWidth = Math.max(5, ...cells.map((c50) => c50.label.length));
39527
+ const idWidth = Math.max(2, ...cells.map((c49) => c49.id.length));
39528
+ const labelWidth = Math.max(5, ...cells.map((c49) => c49.label.length));
38310
39529
  const header = `${"ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} TYPE`;
38311
39530
  const rows = cells.map(
38312
- (c50) => `${c50.id.padEnd(idWidth)} ${c50.label.padEnd(labelWidth)} ${c50.type}`
39531
+ (c49) => `${c49.id.padEnd(idWidth)} ${c49.label.padEnd(labelWidth)} ${c49.type}`
38313
39532
  );
38314
39533
  return { header, rows };
38315
39534
  }
@@ -38365,7 +39584,7 @@ async function runCreate2(options) {
38365
39584
  }
38366
39585
  const organizationId = await resolveOrganizationId();
38367
39586
  const params = { organizationId, environmentId: options.env };
38368
- const label = options.label;
39587
+ const label = options.label?.trim() ? options.label.trim() : void 0;
38369
39588
  let body;
38370
39589
  try {
38371
39590
  body = await buildCreateBody(type, label, options);
@@ -38388,6 +39607,7 @@ async function runCreate2(options) {
38388
39607
  outputCredential(result.body, output);
38389
39608
  }
38390
39609
  async function buildCreateBody(type, label, options) {
39610
+ const labelFields = label !== void 0 ? { label } : {};
38391
39611
  switch (type) {
38392
39612
  case "BEARER_TOKEN": {
38393
39613
  const ac = buildBearerAuthConfig(options);
@@ -38395,7 +39615,7 @@ async function buildCreateBody(type, label, options) {
38395
39615
  const secretValue = await resolveSecret("secret value", "secret", secretOpts(options));
38396
39616
  return {
38397
39617
  credentialType: "BEARER_TOKEN",
38398
- label,
39618
+ ...labelFields,
38399
39619
  secretValue,
38400
39620
  ...ac.value ? { authConfig: ac.value } : {}
38401
39621
  };
@@ -38406,7 +39626,7 @@ async function buildCreateBody(type, label, options) {
38406
39626
  const secretValue = await resolveSecret("secret value", "secret", secretOpts(options));
38407
39627
  return {
38408
39628
  credentialType: "JWT_TOKEN",
38409
- label,
39629
+ ...labelFields,
38410
39630
  secretValue,
38411
39631
  ...ac.value ? { authConfig: ac.value } : {}
38412
39632
  };
@@ -38417,14 +39637,14 @@ async function buildCreateBody(type, label, options) {
38417
39637
  const secretValue = await resolveSecret("secret value", "secret", secretOpts(options));
38418
39638
  return {
38419
39639
  credentialType: "API_KEY",
38420
- label,
39640
+ ...labelFields,
38421
39641
  secretValue,
38422
39642
  ...ac.value ? { authConfig: ac.value } : {}
38423
39643
  };
38424
39644
  }
38425
39645
  case "PASSPHRASE": {
38426
39646
  const secretValue = await resolveSecret("secret value", "secret", secretOpts(options));
38427
- return { credentialType: "PASSPHRASE", label, secretValue };
39647
+ return { credentialType: "PASSPHRASE", ...labelFields, secretValue };
38428
39648
  }
38429
39649
  case "SESSION_STATE": {
38430
39650
  const secretValue = await resolveSecret(
@@ -38432,7 +39652,7 @@ async function buildCreateBody(type, label, options) {
38432
39652
  "secret",
38433
39653
  secretOpts(options)
38434
39654
  );
38435
- return { credentialType: "SESSION_STATE", label, secretValue };
39655
+ return { credentialType: "SESSION_STATE", ...labelFields, secretValue };
38436
39656
  }
38437
39657
  case "COOKIE": {
38438
39658
  const ac = buildCookieAuthConfig(options);
@@ -38440,7 +39660,7 @@ async function buildCreateBody(type, label, options) {
38440
39660
  const secretValue = await resolveSecret("secret value", "secret", secretOpts(options));
38441
39661
  return {
38442
39662
  credentialType: "COOKIE",
38443
- label,
39663
+ ...labelFields,
38444
39664
  secretValue,
38445
39665
  ...ac.value ? { authConfig: ac.value } : {}
38446
39666
  };
@@ -38451,7 +39671,7 @@ async function buildCreateBody(type, label, options) {
38451
39671
  const secretValue = await resolveSecret("test-account email", "secret", secretOpts(options));
38452
39672
  return {
38453
39673
  credentialType: "MAGIC_AUTH",
38454
- label,
39674
+ ...labelFields,
38455
39675
  secretValue,
38456
39676
  ...ac.value ? { authConfig: ac.value } : {}
38457
39677
  };
@@ -38463,7 +39683,7 @@ async function buildCreateBody(type, label, options) {
38463
39683
  const password = await resolveSecret("password", "password", passwordOpts(options));
38464
39684
  return {
38465
39685
  credentialType: "USERNAME_PASSWORD",
38466
- label,
39686
+ ...labelFields,
38467
39687
  username,
38468
39688
  password,
38469
39689
  ...ac.value ? { authConfig: ac.value } : {}
@@ -38616,17 +39836,21 @@ function createCreateCommand2() {
38616
39836
  const command = new Command("create").description("Create a credential in an environment").requiredOption("--env <environmentId>", "Parent environment UUID").requiredOption(
38617
39837
  "--type <type>",
38618
39838
  "bearer-token, jwt-token, api-key, passphrase, username-password, cookie, magic-auth"
38619
- ).requiredOption("--label <label>", "Human-readable label").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table");
39839
+ ).option(
39840
+ "--label <label>",
39841
+ "Human-readable label (optional; defaults from type / username + environment)"
39842
+ ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table");
38620
39843
  addSecretInputOptions(command);
38621
39844
  addAuthConfigOptions(command).addHelpText(
38622
39845
  "after",
38623
39846
  `
38624
39847
  Secrets are read from stdin/file/env (or a no-echo prompt) \u2014 never an inline flag.
39848
+ --label is optional; when omitted the API picks a unique default.
38625
39849
 
38626
39850
  Examples:
38627
- vault read -field=token secret/x | tracecli credential create --env <envId> --type bearer-token --label prod --secret-stdin
39851
+ vault read -field=token secret/x | tracecli credential create --env <envId> --type bearer-token --secret-stdin
38628
39852
  tracecli credential create --env <envId> --type api-key --label "prod key" --secret-env API_KEY --placement header --key-name X-API-Key
38629
- tracecli credential create --env <envId> --type username-password --label login --username-env U --password-env P --method form --login-url https://acme.com/login`
39853
+ tracecli credential create --env <envId> --type username-password --username-env U --password-env P --method form --login-url https://acme.com/login`
38630
39854
  );
38631
39855
  return command.action(async (options) => {
38632
39856
  await runCreate2(options);
@@ -38856,14 +40080,14 @@ function buildEnvironmentTable(envs, leading) {
38856
40080
  url: sanitizeDisplay(e2.primaryUrl)
38857
40081
  }));
38858
40082
  const leadingWidths = leading.map(
38859
- (column, i) => Math.max(column.header.length, ...cells.map((c50) => c50.leading[i]?.length ?? 0))
40083
+ (column, i) => Math.max(column.header.length, ...cells.map((c49) => c49.leading[i]?.length ?? 0))
38860
40084
  );
38861
- const nameWidth = Math.max(4, ...cells.map((c50) => c50.name.length));
40085
+ const nameWidth = Math.max(4, ...cells.map((c49) => c49.name.length));
38862
40086
  const padLeading = (values) => values.map((v2, i) => v2.padEnd(leadingWidths[i] ?? 0)).join(" ");
38863
40087
  return {
38864
- header: `${padLeading(leading.map((c50) => c50.header))} ${"NAME".padEnd(nameWidth)} PROD URL`,
40088
+ header: `${padLeading(leading.map((c49) => c49.header))} ${"NAME".padEnd(nameWidth)} PROD URL`,
38865
40089
  rows: cells.map(
38866
- (c50) => `${padLeading(c50.leading)} ${c50.name.padEnd(nameWidth)} ${c50.prod.padEnd(4)} ${c50.url}`
40090
+ (c49) => `${padLeading(c49.leading)} ${c49.name.padEnd(nameWidth)} ${c49.prod.padEnd(4)} ${c49.url}`
38867
40091
  )
38868
40092
  };
38869
40093
  }
@@ -39225,6 +40449,120 @@ var packageMetadata = readPackageMetadata();
39225
40449
  var CLI_PACKAGE_NAME = packageMetadata.name;
39226
40450
  var CLI_VERSION = packageMetadata.version;
39227
40451
 
40452
+ // src/pentest/commands.ts
40453
+ import { readFileSync as readFileSync4 } from "fs";
40454
+
40455
+ // src/pentest/questionnaire.ts
40456
+ function buildUpsertQuestionnaireBody(answers) {
40457
+ if (answers === null) {
40458
+ return { answers: null };
40459
+ }
40460
+ return { answers: normalizePentestCustomerIntakeAnswers(answers) };
40461
+ }
40462
+ function parseQuestionnaireAnswersFile(raw, sourceLabel = "file") {
40463
+ let parsed;
40464
+ try {
40465
+ parsed = JSON.parse(raw);
40466
+ } catch {
40467
+ return { error: `Invalid JSON in ${sourceLabel}.` };
40468
+ }
40469
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
40470
+ return {
40471
+ error: `${sourceLabel} must be a JSON object of questionnaire answers (not an array or null).`
40472
+ };
40473
+ }
40474
+ if ("answers" in parsed && Object.keys(parsed).length === 1) {
40475
+ return {
40476
+ error: `${sourceLabel} must contain the answers object directly, not an { answers } wrapper. The CLI wraps it for the API.`
40477
+ };
40478
+ }
40479
+ const result = PentestCustomerIntakeAnswersSchema.safeParse(parsed);
40480
+ if (!result.success) {
40481
+ const issue2 = result.error.issues[0];
40482
+ const path = issue2?.path.length ? issue2.path.join(".") : "answers";
40483
+ const message = issue2?.message ?? "invalid";
40484
+ return { error: `Invalid questionnaire answers in ${sourceLabel}: ${path} \u2014 ${message}` };
40485
+ }
40486
+ return { answers: normalizePentestCustomerIntakeAnswers(result.data) };
40487
+ }
40488
+ function applyTextIntakeAnswer(current, key, input) {
40489
+ const trimmed = input.trim();
40490
+ if (trimmed.length === 0) {
40491
+ return current;
40492
+ }
40493
+ return { ...current, [key]: trimmed };
40494
+ }
40495
+ function applyMultiSelectIntakeAnswer(current, key, selected) {
40496
+ if (selected.length === 0) {
40497
+ return current;
40498
+ }
40499
+ return {
40500
+ ...current,
40501
+ [key]: Array.from(new Set(selected)).sort()
40502
+ };
40503
+ }
40504
+ function formatIssueClasses(values) {
40505
+ return values.map((v2) => INTAKE_ISSUE_CLASS_LABELS[v2]).join(", ");
40506
+ }
40507
+ function formatSurfaces(values) {
40508
+ return values.map((v2) => INTAKE_SURFACE_LABELS[v2]).join(", ");
40509
+ }
40510
+ function formatAnswerValue(key, answers) {
40511
+ switch (key) {
40512
+ case "issueClasses": {
40513
+ const values = answers.issueClasses;
40514
+ if (values === void 0 || values.length === 0) {
40515
+ return null;
40516
+ }
40517
+ return formatIssueClasses(values);
40518
+ }
40519
+ case "surfaces": {
40520
+ const values = answers.surfaces;
40521
+ if (values === void 0 || values.length === 0) {
40522
+ return null;
40523
+ }
40524
+ return formatSurfaces(values);
40525
+ }
40526
+ default: {
40527
+ const value = answers[key];
40528
+ if (value === void 0 || value.trim().length === 0) {
40529
+ return null;
40530
+ }
40531
+ return value;
40532
+ }
40533
+ }
40534
+ }
40535
+ function formatQuestionnaireShow(input) {
40536
+ const lines = [
40537
+ `Editable: ${input.editable ? "yes (scoping window open)" : "no (editing closed after scoping)"}`
40538
+ ];
40539
+ if (input.answers === null || Object.keys(input.answers).length === 0) {
40540
+ lines.push("Answers: (none)");
40541
+ return lines.join("\n");
40542
+ }
40543
+ lines.push("Answers:");
40544
+ for (const key of PENTEST_CUSTOMER_INTAKE_ANSWER_KEYS) {
40545
+ const value = formatAnswerValue(key, input.answers);
40546
+ if (value === null) {
40547
+ continue;
40548
+ }
40549
+ lines.push(`${PENTEST_CUSTOMER_INTAKE_LABELS[key]}: ${value}`);
40550
+ }
40551
+ return lines.join("\n");
40552
+ }
40553
+ function issueClassSelectOptions() {
40554
+ return CustomerPriorityCategoryValues.map((value) => ({
40555
+ value,
40556
+ label: INTAKE_ISSUE_CLASS_LABELS[value]
40557
+ }));
40558
+ }
40559
+ function surfaceSelectOptions() {
40560
+ return IntakeSurfaceValues.map((value) => ({
40561
+ value,
40562
+ label: INTAKE_SURFACE_LABELS[value]
40563
+ }));
40564
+ }
40565
+
39228
40566
  // src/pentest/commands.ts
39229
40567
  var DEFAULT_LIMIT2 = 30;
39230
40568
  var MAX_LIMIT2 = 100;
@@ -39474,13 +40812,9 @@ Examples:
39474
40812
  console.error(`Failed to list findings (status ${result.status}).`);
39475
40813
  process.exit(1);
39476
40814
  }
39477
- let filteredFindings = result.body;
39478
- if (options.severity) {
39479
- filteredFindings = filteredFindings.filter((f) => f.severity === options.severity);
39480
- }
39481
- if (options.status) {
39482
- filteredFindings = filteredFindings.filter((f) => f.status === options.status);
39483
- }
40815
+ const filteredFindings = result.body.filter(
40816
+ (f) => (!options.severity || f.severity === options.severity) && (!options.status || f.status === options.status)
40817
+ );
39484
40818
  filteredFindings.sort(
39485
40819
  (a, b2) => (SEVERITY_ORDER[a.severity] ?? 99) - (SEVERITY_ORDER[b2.severity] ?? 99)
39486
40820
  );
@@ -39764,16 +41098,248 @@ Examples:
39764
41098
  await runUpdate4(pentestId, options);
39765
41099
  });
39766
41100
  }
41101
+ function failQuestionnaire(status, body, action, notFoundLabel) {
41102
+ const { message } = readErrorFields(body);
41103
+ if (status === UNAUTHORIZED_STATUS2) {
41104
+ exitAuthFailed();
41105
+ }
41106
+ if (status === 403) {
41107
+ if (action === "write") {
41108
+ console.error(
41109
+ colorError(
41110
+ "Questionnaire writes require an organization admin (or Trace staff) signed in with a browser session. API keys are rejected."
41111
+ )
41112
+ );
41113
+ } else {
41114
+ console.error(
41115
+ colorError(message ?? "You do not have permission to view this questionnaire.")
41116
+ );
41117
+ }
41118
+ process.exit(1);
41119
+ }
41120
+ if (status === 404) {
41121
+ console.error(
41122
+ colorError(notFoundLabel ? `Pentest not found: ${notFoundLabel}.` : message ?? "Not found.")
41123
+ );
41124
+ process.exit(1);
41125
+ }
41126
+ if (status === 400) {
41127
+ console.error(colorError(message ?? "Invalid questionnaire answers (status 400)."));
41128
+ process.exit(1);
41129
+ }
41130
+ if (status === 409) {
41131
+ console.error(
41132
+ colorError(
41133
+ message ?? "Questionnaire editing is closed after scoping starts (current stage is past DRAFT / READY_TO_START)."
41134
+ )
41135
+ );
41136
+ process.exit(1);
41137
+ }
41138
+ console.error(
41139
+ colorError(
41140
+ `Failed to ${action === "write" ? "save" : "load"} questionnaire (status ${status})${message ? `: ${message}` : "."}`
41141
+ )
41142
+ );
41143
+ process.exit(1);
41144
+ }
41145
+ async function fetchQuestionnaire(organizationId, pentestId, notFoundLabel) {
41146
+ const result = await authenticatedRequest(
41147
+ (client) => client.pentest.getCustomerPentestQuestionnaire({
41148
+ params: { organizationId, pentestId }
41149
+ })
41150
+ );
41151
+ if (result.status !== SUCCESS_STATUS) {
41152
+ failQuestionnaire(result.status, result.body, "read", notFoundLabel);
41153
+ }
41154
+ return result.body;
41155
+ }
41156
+ async function putQuestionnaire(organizationId, pentestId, answers, notFoundLabel) {
41157
+ const body = buildUpsertQuestionnaireBody(answers);
41158
+ const result = await authenticatedRequest(
41159
+ (client) => client.pentest.upsertCustomerPentestQuestionnaire({
41160
+ params: { organizationId, pentestId },
41161
+ body
41162
+ })
41163
+ );
41164
+ if (result.status !== SUCCESS_STATUS) {
41165
+ failQuestionnaire(result.status, result.body, "write", notFoundLabel);
41166
+ }
41167
+ return result.body;
41168
+ }
41169
+ function exitCancelled() {
41170
+ me("Cancelled.");
41171
+ process.exit(1);
41172
+ }
41173
+ async function promptQuestionnaireAnswers(existing) {
41174
+ let working = { ...existing ?? {} };
41175
+ const textPrompt = async (key, question) => {
41176
+ const previous = existing?.[key];
41177
+ const value = await fe({
41178
+ message: `${question} (empty keeps previous)`,
41179
+ initialValue: previous ?? "",
41180
+ ...previous ? {} : { placeholder: "Optional, press Enter to skip" }
41181
+ });
41182
+ if (q(value)) {
41183
+ exitCancelled();
41184
+ }
41185
+ working = applyTextIntakeAnswer(working, key, value);
41186
+ };
41187
+ for (const entry of PENTEST_CUSTOMER_INTAKE_QUESTIONS) {
41188
+ if (entry.kind === "text") {
41189
+ await textPrompt(entry.key, entry.question);
41190
+ continue;
41191
+ }
41192
+ const message = `${entry.question} (empty keeps previous)`;
41193
+ if (entry.key === "issueClasses") {
41194
+ const selected = await ve({
41195
+ message,
41196
+ options: issueClassSelectOptions(),
41197
+ required: false,
41198
+ ...existing?.issueClasses !== void 0 ? { initialValues: existing.issueClasses } : {}
41199
+ });
41200
+ if (q(selected)) {
41201
+ exitCancelled();
41202
+ }
41203
+ working = applyMultiSelectIntakeAnswer(working, "issueClasses", selected);
41204
+ } else {
41205
+ const selected = await ve({
41206
+ message,
41207
+ options: surfaceSelectOptions(),
41208
+ required: false,
41209
+ ...existing?.surfaces !== void 0 ? { initialValues: existing.surfaces } : {}
41210
+ });
41211
+ if (q(selected)) {
41212
+ exitCancelled();
41213
+ }
41214
+ working = applyMultiSelectIntakeAnswer(working, "surfaces", selected);
41215
+ }
41216
+ await textPrompt(entry.notesKey, entry.notesQuestion);
41217
+ }
41218
+ const confirmed = await ue({
41219
+ message: "Save questionnaire answers?",
41220
+ initialValue: true
41221
+ });
41222
+ if (q(confirmed) || !confirmed) {
41223
+ exitCancelled();
41224
+ }
41225
+ if (Object.keys(working).length === 0) {
41226
+ return existing === null ? null : working;
41227
+ }
41228
+ return working;
41229
+ }
41230
+ async function runQuestionnaire(pentestIdentifier, options) {
41231
+ const show = options.show === true;
41232
+ const filePath = options.file;
41233
+ if (show && filePath !== void 0) {
41234
+ console.error(colorError("Pass only one of --show / --file."));
41235
+ process.exit(1);
41236
+ }
41237
+ const isInteractiveTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
41238
+ if (!show && filePath === void 0 && !isInteractiveTty) {
41239
+ console.error(
41240
+ colorError(
41241
+ "Interactive questionnaire requires a TTY. Use --show to print answers, or --file <path> to upload an answers JSON object."
41242
+ )
41243
+ );
41244
+ process.exit(2);
41245
+ }
41246
+ const organizationId = await resolveOrganizationId();
41247
+ const pentestId = await resolvePentestId(organizationId, pentestIdentifier);
41248
+ if (show) {
41249
+ const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
41250
+ console.log(
41251
+ formatQuestionnaireShow({
41252
+ answers: current2.answers,
41253
+ editable: current2.editable
41254
+ })
41255
+ );
41256
+ return;
41257
+ }
41258
+ if (filePath !== void 0) {
41259
+ let raw;
41260
+ try {
41261
+ raw = readFileSync4(filePath, "utf8");
41262
+ } catch {
41263
+ console.error(colorError(`Could not read answers file: ${filePath}`));
41264
+ process.exit(1);
41265
+ }
41266
+ const parsed = parseQuestionnaireAnswersFile(raw, filePath);
41267
+ if ("error" in parsed) {
41268
+ console.error(colorError(parsed.error));
41269
+ process.exit(1);
41270
+ }
41271
+ const saved2 = await putQuestionnaire(
41272
+ organizationId,
41273
+ pentestId,
41274
+ parsed.answers,
41275
+ pentestIdentifier
41276
+ );
41277
+ console.log(colorSuccess("Saved questionnaire answers."));
41278
+ console.log(
41279
+ formatQuestionnaireShow({
41280
+ answers: saved2.answers,
41281
+ editable: saved2.editable
41282
+ })
41283
+ );
41284
+ return;
41285
+ }
41286
+ const current = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
41287
+ if (!current.editable) {
41288
+ console.error(
41289
+ colorError(
41290
+ "Questionnaire editing is closed after scoping starts. Use --show to view the saved answers."
41291
+ )
41292
+ );
41293
+ process.exit(1);
41294
+ }
41295
+ const merged = await promptQuestionnaireAnswers(current.answers);
41296
+ const saved = await putQuestionnaire(organizationId, pentestId, merged, pentestIdentifier);
41297
+ console.log(colorSuccess("Saved questionnaire answers."));
41298
+ console.log(
41299
+ formatQuestionnaireShow({
41300
+ answers: saved.answers,
41301
+ editable: saved.editable
41302
+ })
41303
+ );
41304
+ }
41305
+ function createQuestionnaireCommand() {
41306
+ return new Command("questionnaire").description("View or fill the customer pentest intake questionnaire").argument("<pentestId>", "Pentest display ID (e.g. PT-3) or UUID").option("--show", "Print current answers using shared labels (GET)").option(
41307
+ "--file <path>",
41308
+ "Upload answers from a JSON file (answers object only \u2014 not { answers }; whole-object replace)"
41309
+ ).addHelpText(
41310
+ "after",
41311
+ `
41312
+ Optional scoping questionnaire for a new pentest (editable while stage is DRAFT
41313
+ or READY_TO_START). Reads accept a browser session or API key; writes require an
41314
+ organization admin (or Trace staff) signed in via \`tracecli auth login\` \u2014 API
41315
+ keys are rejected.
41316
+
41317
+ Modes:
41318
+ --show GET and print key: value with shared customer-facing labels
41319
+ --file <path> PUT the file's answers object (CLI wraps { answers } for the API)
41320
+ (TTY default) load-merge-write: GET, prompt each question, confirm, PUT
41321
+
41322
+ Non-interactive shells must pass --show or --file (exit 2 otherwise).
41323
+
41324
+ Examples:
41325
+ tracecli pentest questionnaire PT-1 --show
41326
+ tracecli pentest questionnaire PT-1 --file answers.json
41327
+ tracecli pentest questionnaire PT-1`
41328
+ ).action(async (pentestId, options) => {
41329
+ await runQuestionnaire(pentestId, options);
41330
+ });
41331
+ }
39767
41332
  function createPentestCommand() {
39768
41333
  const pentest = new Command("pentest").description("Manage pentests");
39769
41334
  pentest.addCommand(createListCommand5());
39770
41335
  pentest.addCommand(createUpdateCommand4());
41336
+ pentest.addCommand(createQuestionnaireCommand());
39771
41337
  pentest.addCommand(createFindingsCommand());
39772
41338
  return pentest;
39773
41339
  }
39774
41340
 
39775
41341
  // src/update/commands.ts
39776
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
41342
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
39777
41343
  import { dirname as dirname2, join as join5 } from "path";
39778
41344
  import { createInterface as createInterface4 } from "readline/promises";
39779
41345
  var UPDATE_CACHE_FILE = "update-check.json";
@@ -39791,7 +41357,7 @@ function readUpdateCheckCache() {
39791
41357
  return null;
39792
41358
  }
39793
41359
  try {
39794
- const rawCache = readFileSync4(cachePath, "utf-8");
41360
+ const rawCache = readFileSync5(cachePath, "utf-8");
39795
41361
  const parsed = JSON.parse(rawCache);
39796
41362
  if (typeof parsed !== "object" || parsed === null) {
39797
41363
  return null;
@@ -40521,177 +42087,10 @@ Examples:
40521
42087
  }
40522
42088
  );
40523
42089
  }
40524
- var FIX_PR_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
40525
- "PR_OPENED",
40526
- "NO_SAFE_FIX",
40527
- "PATCH_TOO_LARGE",
40528
- "TESTS_FAILED",
40529
- "INFRA_FAILED",
40530
- "STALE_VULN",
40531
- "TIMED_OUT",
40532
- "CANCELLED"
40533
- ]);
40534
- var FIX_PR_AGENT_FLAVORS = ["claude-code", "codex"];
40535
- var FIX_PR_POLL_MS = 3e3;
40536
- function isFixPrAgentFlavor(value) {
40537
- return FIX_PR_AGENT_FLAVORS.includes(value);
40538
- }
40539
- function formatFixPrWatchLine(row) {
40540
- return `[${row.status}] run=${row.runId.slice(0, 8)} agent=${row.agentFlavor}`;
40541
- }
40542
- function formatFixPrTerminalLines(row) {
40543
- const link = row.prUrl ? ` ${row.prUrl}` : "";
40544
- const lines = [`Run ${row.runId} \u2192 ${row.status}${link}`];
40545
- if (row.statusDetails) {
40546
- lines.push(`Details: ${row.statusDetails}`);
40547
- }
40548
- return lines;
40549
- }
40550
- function createFixCommand() {
40551
- return new Command("fix").description("Trigger a Trace auto-fix PR for the vulnerability (staff-only dogfood)").argument("<id>", "Vulnerability display ID (e.g. TRACE-123 or 123) or UUID").option(
40552
- "--agent <flavor>",
40553
- `Override the agent flavor (one of: ${FIX_PR_AGENT_FLAVORS.join(", ")})`
40554
- ).option("--watch", "Poll the run until it reaches a terminal status", false).option("-o, --output <format>", "Output format: table, json (default: table)", "table").addHelpText(
40555
- "after",
40556
- `
40557
- Examples:
40558
- # Trigger and exit immediately (scripting):
40559
- tracecli vulnerabilities fix TRACE-123
40560
-
40561
- # Trigger and watch until terminal:
40562
- tracecli vulnerabilities fix TRACE-123 --watch
40563
-
40564
- # Trigger with codex agent (requires AZURE_OPENAI_ENABLED):
40565
- tracecli vulnerabilities fix TRACE-123 --agent codex
40566
-
40567
- # JSON output (final row to stdout; rolling status to stderr under --watch):
40568
- tracecli vulnerabilities fix TRACE-123 --watch --output json | jq .`
40569
- ).action(
40570
- async (identifier, options) => {
40571
- if (options.agent && !isFixPrAgentFlavor(options.agent)) {
40572
- console.error(
40573
- `Invalid --agent '${options.agent}'. Expected one of: ${FIX_PR_AGENT_FLAVORS.join(", ")}.`
40574
- );
40575
- process.exit(1);
40576
- }
40577
- if (options.output !== "table" && options.output !== "json") {
40578
- console.error(`Invalid --output '${options.output}'. Expected one of: table, json.`);
40579
- process.exit(1);
40580
- }
40581
- const organizationId = await resolveOrganizationId();
40582
- const triggerResult = await authenticatedRequest(
40583
- (client) => client.vulnerabilityFixRun.triggerRun({
40584
- params: { organizationId, vulnerabilityId: identifier },
40585
- body: options.agent ? { agentFlavor: options.agent } : {}
40586
- })
40587
- );
40588
- if (triggerResult.status === 401) {
40589
- console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
40590
- process.exit(1);
40591
- }
40592
- if (triggerResult.status === 403) {
40593
- console.error(
40594
- "Forbidden \u2014 vulnerability fix-PR is a staff-only feature gated by the AUTO_FIX_PR capability."
40595
- );
40596
- process.exit(1);
40597
- }
40598
- if (triggerResult.status === 404) {
40599
- console.error(
40600
- `Vulnerability '${identifier}' not found.
40601
- Run \`tracecli vulnerabilities list\` to see available vulnerabilities.`
40602
- );
40603
- process.exit(1);
40604
- }
40605
- if (triggerResult.status === 429) {
40606
- const body = triggerResult.body;
40607
- const retry = body.details?.retryAfterSeconds;
40608
- console.error(
40609
- `Daily cap exceeded. ${retry ? `Try again in ${retry}s.` : body.message ?? ""}`
40610
- );
40611
- process.exit(1);
40612
- }
40613
- if (triggerResult.status === 400) {
40614
- const body = triggerResult.body;
40615
- console.error(`Bad request: ${body.message ?? "invalid input"}`);
40616
- process.exit(1);
40617
- }
40618
- if (triggerResult.status !== 201) {
40619
- console.error(`Failed to trigger fix-PR run (status ${triggerResult.status}).`);
40620
- process.exit(1);
40621
- }
40622
- const triggered = triggerResult.body;
40623
- if (!options.watch) {
40624
- if (options.output === "json") {
40625
- console.log(JSON.stringify(triggered, null, 2));
40626
- } else {
40627
- console.log(
40628
- `Triggered fix-PR run ${triggered.runId} (status=${triggered.status}, agent=${triggered.agentFlavor})`
40629
- );
40630
- }
40631
- process.exit(0);
40632
- }
40633
- let latest = triggered;
40634
- const isTTY = process.stderr.isTTY ?? false;
40635
- const onSigint = () => {
40636
- const msg = `
40637
- watch interrupted; run id=${latest.runId}`;
40638
- process.stderr.write(`${msg}
40639
- `);
40640
- process.exit(130);
40641
- };
40642
- process.on("SIGINT", onSigint);
40643
- const writeRolling = (row) => {
40644
- const line = formatFixPrWatchLine(row);
40645
- if (isTTY && options.output !== "json") {
40646
- process.stderr.write(`\r${line} `);
40647
- } else {
40648
- process.stderr.write(`${line}
40649
- `);
40650
- }
40651
- };
40652
- while (!FIX_PR_TERMINAL_STATUSES.has(latest.status)) {
40653
- writeRolling(latest);
40654
- await new Promise((resolve) => setTimeout(resolve, FIX_PR_POLL_MS));
40655
- const pollResult = await authenticatedRequest(
40656
- (client) => client.vulnerabilityFixRun.getRun({
40657
- params: {
40658
- organizationId,
40659
- vulnerabilityId: identifier,
40660
- runId: latest.runId
40661
- }
40662
- })
40663
- );
40664
- if (pollResult.status === 401) {
40665
- process.stderr.write("\n");
40666
- console.error("Authentication failed during watch. Run `tracecli auth login`.");
40667
- process.exit(1);
40668
- }
40669
- if (pollResult.status !== 200) {
40670
- process.stderr.write("\n");
40671
- console.error(`Failed to read run status (status ${pollResult.status}).`);
40672
- process.exit(1);
40673
- }
40674
- latest = pollResult.body;
40675
- }
40676
- if (isTTY && options.output !== "json") {
40677
- process.stderr.write("\n");
40678
- }
40679
- if (options.output === "json") {
40680
- console.log(JSON.stringify(latest, null, 2));
40681
- } else {
40682
- for (const line of formatFixPrTerminalLines(latest)) {
40683
- console.log(line);
40684
- }
40685
- }
40686
- process.exit(latest.status === "PR_OPENED" ? 0 : 2);
40687
- }
40688
- );
40689
- }
40690
42090
  function createVulnerabilitiesCommand() {
40691
42091
  const vulnerabilities = new Command("vulnerabilities").description("Manage vulnerabilities");
40692
42092
  vulnerabilities.addCommand(createGetCommand4());
40693
42093
  vulnerabilities.addCommand(createListCommand6());
40694
- vulnerabilities.addCommand(createFixCommand());
40695
42094
  return vulnerabilities;
40696
42095
  }
40697
42096