From 0d765f56f7f52f0e8bb79ff19b74c07c89d542d1 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Wed, 29 Oct 2025 12:34:41 +0500 Subject: [PATCH 001/255] chore: update terraform to 1.13.4 (#20532) Co-authored-by: Claude Co-authored-by: Ethan Dickson --- .github/actions/setup-tf/action.yaml | 2 +- .github/workflows/dogfood.yaml | 2 +- dogfood/coder/Dockerfile | 2 +- install.sh | 2 +- provisioner/terraform/install.go | 2 +- provisioner/terraform/testdata/resources/version.txt | 2 +- provisioner/terraform/testdata/version.txt | 2 +- scripts/Dockerfile.base | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/actions/setup-tf/action.yaml b/.github/actions/setup-tf/action.yaml index 6f8c8c32cf38c..f79618834d9a1 100644 --- a/.github/actions/setup-tf/action.yaml +++ b/.github/actions/setup-tf/action.yaml @@ -7,5 +7,5 @@ runs: - name: Install Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 with: - terraform_version: 1.13.0 + terraform_version: 1.13.4 terraform_wrapper: false diff --git a/.github/workflows/dogfood.yaml b/.github/workflows/dogfood.yaml index 2f47132ae43f0..e1d4a7a22787a 100644 --- a/.github/workflows/dogfood.yaml +++ b/.github/workflows/dogfood.yaml @@ -40,7 +40,7 @@ jobs: with: # Pinning to 2.28 here, as Nix gets a "error: [json.exception.type_error.302] type must be array, but is string" # on version 2.29 and above. - nix_version: "2.28.4" + nix_version: "2.28.5" - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3 with: diff --git a/dogfood/coder/Dockerfile b/dogfood/coder/Dockerfile index 932e2ce9b06a6..a8fe252554c8f 100644 --- a/dogfood/coder/Dockerfile +++ b/dogfood/coder/Dockerfile @@ -209,7 +209,7 @@ RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.edge.kernel.org/u # NOTE: In scripts/Dockerfile.base we specifically install Terraform version 1.12.2. # Installing the same version here to match. -RUN wget -O /tmp/terraform.zip "https://releases.hashicorp.com/terraform/1.13.0/terraform_1.13.0_linux_amd64.zip" && \ +RUN wget -O /tmp/terraform.zip "https://releases.hashicorp.com/terraform/1.13.4/terraform_1.13.4_linux_amd64.zip" && \ unzip /tmp/terraform.zip -d /usr/local/bin && \ rm -f /tmp/terraform.zip && \ chmod +x /usr/local/bin/terraform && \ diff --git a/install.sh b/install.sh index 1dbf813b96690..99752791a90ae 100755 --- a/install.sh +++ b/install.sh @@ -273,7 +273,7 @@ EOF main() { MAINLINE=1 STABLE=0 - TERRAFORM_VERSION="1.13.0" + TERRAFORM_VERSION="1.13.4" if [ "${TRACE-}" ]; then set -x diff --git a/provisioner/terraform/install.go b/provisioner/terraform/install.go index 63d6b0278231d..174fcf257d54f 100644 --- a/provisioner/terraform/install.go +++ b/provisioner/terraform/install.go @@ -22,7 +22,7 @@ var ( // when Terraform is not available on the system. // NOTE: Keep this in sync with the version in scripts/Dockerfile.base. // NOTE: Keep this in sync with the version in install.sh. - TerraformVersion = version.Must(version.NewVersion("1.13.0")) + TerraformVersion = version.Must(version.NewVersion("1.13.4")) minTerraformVersion = version.Must(version.NewVersion("1.1.0")) maxTerraformVersion = version.Must(version.NewVersion("1.13.9")) // use .9 to automatically allow patch releases diff --git a/provisioner/terraform/testdata/resources/version.txt b/provisioner/terraform/testdata/resources/version.txt index feaae22bac7e9..80138e7146693 100644 --- a/provisioner/terraform/testdata/resources/version.txt +++ b/provisioner/terraform/testdata/resources/version.txt @@ -1 +1 @@ -1.13.0 +1.13.4 diff --git a/provisioner/terraform/testdata/version.txt b/provisioner/terraform/testdata/version.txt index feaae22bac7e9..80138e7146693 100644 --- a/provisioner/terraform/testdata/version.txt +++ b/provisioner/terraform/testdata/version.txt @@ -1 +1 @@ -1.13.0 +1.13.4 diff --git a/scripts/Dockerfile.base b/scripts/Dockerfile.base index d14d88e1a544d..ca48243cdfe09 100644 --- a/scripts/Dockerfile.base +++ b/scripts/Dockerfile.base @@ -26,7 +26,7 @@ RUN apk add --no-cache \ # Terraform was disabled in the edge repo due to a build issue. # https://gitlab.alpinelinux.org/alpine/aports/-/commit/f3e263d94cfac02d594bef83790c280e045eba35 # Using wget for now. Note that busybox unzip doesn't support streaming. -RUN ARCH="$(arch)"; if [ "${ARCH}" == "x86_64" ]; then ARCH="amd64"; elif [ "${ARCH}" == "aarch64" ]; then ARCH="arm64"; elif [ "${ARCH}" == "armv7l" ]; then ARCH="arm"; fi; wget -O /tmp/terraform.zip "https://releases.hashicorp.com/terraform/1.13.0/terraform_1.13.0_linux_${ARCH}.zip" && \ +RUN ARCH="$(arch)"; if [ "${ARCH}" == "x86_64" ]; then ARCH="amd64"; elif [ "${ARCH}" == "aarch64" ]; then ARCH="arm64"; elif [ "${ARCH}" == "armv7l" ]; then ARCH="arm"; fi; wget -O /tmp/terraform.zip "https://releases.hashicorp.com/terraform/1.13.4/terraform_1.13.4_linux_${ARCH}.zip" && \ busybox unzip /tmp/terraform.zip -d /usr/local/bin && \ rm -f /tmp/terraform.zip && \ chmod +x /usr/local/bin/terraform && \ From c3e3bb58f2821c94b31bbe8bb750849514b49a5b Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Wed, 29 Oct 2025 10:37:28 +0000 Subject: [PATCH 002/255] feat: delete pending canceled prebuilds (#20499) ## Description PR https://github.com/coder/coder/pull/20387 introduced canceling pending prebuild jobs from inactive template versions to avoid provisioning obsolete workspaces. However, the associated prebuilds remained in the database with "Canceled" status, visible in the UI. This PR now orphan-deletes these canceled prebuilt workspaces. Since the canceled jobs were never processed by a provisioner, no Terraform resources were created, making orphan deletion safe. Orphan deletion always creates a provisioner job, but behaves differently based on provisioner availability: - If no provisioner daemon is available, the job is immediately marked as completed and the workspace is marked as deleted without any provisioner processing - If a provisioner daemon is available, it processes the delete job with empty Terraform state (no actual resources to destroy) The job cancellation and workspace deletion occur atomically in the same transaction. We don't split this into two separate reconciliation runs because there's no way to distinguish between system-canceled prebuilds and user-canceled workspaces. If we deleted canceled workspaces in a later run, we'd delete user-canceled workspaces that users may want to keep for troubleshooting. Note: This only applies to system-generated prebuilds from inactive template versions. ## Changes * Update `UpdatePrebuildProvisionerJobWithCancel` query to return job ID, workspace ID, template ID, and template version preset ID * Add `DeprovisionMode` enum to support orphan deletion in the provision flow * Update `ActionTypeCancelPending` handler to cancel jobs and orphan-delete associated workspaces atomically --- coderd/database/dbauthz/dbauthz.go | 4 +- coderd/database/dbauthz/dbauthz_test.go | 9 +- coderd/database/dbmetrics/querymetrics.go | 2 +- coderd/database/dbmock/dbmock.go | 4 +- coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 38 +++-- coderd/database/queries/prebuilds.sql | 16 +- enterprise/coderd/prebuilds/reconcile.go | 144 +++++++++++++----- enterprise/coderd/prebuilds/reconcile_test.go | 95 ++++++++---- 9 files changed, 219 insertions(+), 95 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 01d3c4f26ae85..2081fbf5a8aac 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -4933,10 +4933,10 @@ func (q *querier) UpdateOrganizationDeletedByID(ctx context.Context, arg databas return deleteQ(q.log, q.auth, q.db.GetOrganizationByID, deleteF)(ctx, arg.ID) } -func (q *querier) UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg database.UpdatePrebuildProvisionerJobWithCancelParams) ([]uuid.UUID, error) { +func (q *querier) UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg database.UpdatePrebuildProvisionerJobWithCancelParams) ([]database.UpdatePrebuildProvisionerJobWithCancelRow, error) { // Prebuild operation for canceling pending prebuild jobs from non-active template versions if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourcePrebuiltWorkspace); err != nil { - return []uuid.UUID{}, err + return []database.UpdatePrebuildProvisionerJobWithCancelRow{}, err } return q.db.UpdatePrebuildProvisionerJobWithCancel(ctx, arg) } diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 8cf622a4347f3..63226271f7fa0 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -646,10 +646,13 @@ func (s *MethodTestSuite) TestProvisionerJob() { PresetID: uuid.NullUUID{UUID: uuid.New(), Valid: true}, Now: dbtime.Now(), } - jobIDs := []uuid.UUID{uuid.New(), uuid.New()} + canceledJobs := []database.UpdatePrebuildProvisionerJobWithCancelRow{ + {ID: uuid.New(), WorkspaceID: uuid.New(), TemplateID: uuid.New(), TemplateVersionPresetID: uuid.NullUUID{UUID: uuid.New(), Valid: true}}, + {ID: uuid.New(), WorkspaceID: uuid.New(), TemplateID: uuid.New(), TemplateVersionPresetID: uuid.NullUUID{UUID: uuid.New(), Valid: true}}, + } - dbm.EXPECT().UpdatePrebuildProvisionerJobWithCancel(gomock.Any(), arg).Return(jobIDs, nil).AnyTimes() - check.Args(arg).Asserts(rbac.ResourcePrebuiltWorkspace, policy.ActionUpdate).Returns(jobIDs) + dbm.EXPECT().UpdatePrebuildProvisionerJobWithCancel(gomock.Any(), arg).Return(canceledJobs, nil).AnyTimes() + check.Args(arg).Asserts(rbac.ResourcePrebuiltWorkspace, policy.ActionUpdate).Returns(canceledJobs) })) s.Run("GetProvisionerJobsByIDs", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { org := testutil.Fake(s.T(), faker, database.Organization{}) diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 1bd8fda62470a..1b36a3fa987f2 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -3042,7 +3042,7 @@ func (m queryMetricsStore) UpdateOrganizationDeletedByID(ctx context.Context, ar return r0 } -func (m queryMetricsStore) UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg database.UpdatePrebuildProvisionerJobWithCancelParams) ([]uuid.UUID, error) { +func (m queryMetricsStore) UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg database.UpdatePrebuildProvisionerJobWithCancelParams) ([]database.UpdatePrebuildProvisionerJobWithCancelRow, error) { start := time.Now() r0, r1 := m.s.UpdatePrebuildProvisionerJobWithCancel(ctx, arg) m.queryLatencies.WithLabelValues("UpdatePrebuildProvisionerJobWithCancel").Observe(time.Since(start).Seconds()) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 1983092aa53f0..0ecd0191cbce1 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -6540,10 +6540,10 @@ func (mr *MockStoreMockRecorder) UpdateOrganizationDeletedByID(ctx, arg any) *go } // UpdatePrebuildProvisionerJobWithCancel mocks base method. -func (m *MockStore) UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg database.UpdatePrebuildProvisionerJobWithCancelParams) ([]uuid.UUID, error) { +func (m *MockStore) UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg database.UpdatePrebuildProvisionerJobWithCancelParams) ([]database.UpdatePrebuildProvisionerJobWithCancelRow, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdatePrebuildProvisionerJobWithCancel", ctx, arg) - ret0, _ := ret[0].([]uuid.UUID) + ret0, _ := ret[0].([]database.UpdatePrebuildProvisionerJobWithCancelRow) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 2b96823028f61..2aa1cb8650051 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -667,7 +667,7 @@ type sqlcQuerier interface { // Cancels all pending provisioner jobs for prebuilt workspaces on a specific preset from an // inactive template version. // This is an optimization to clean up stale pending jobs. - UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg UpdatePrebuildProvisionerJobWithCancelParams) ([]uuid.UUID, error) + UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg UpdatePrebuildProvisionerJobWithCancelParams) ([]UpdatePrebuildProvisionerJobWithCancelRow, error) UpdatePresetPrebuildStatus(ctx context.Context, arg UpdatePresetPrebuildStatusParams) error UpdateProvisionerDaemonLastSeenAt(ctx context.Context, arg UpdateProvisionerDaemonLastSeenAtParams) error UpdateProvisionerJobByID(ctx context.Context, arg UpdateProvisionerJobByIDParams) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 2005f98347c6c..2869316266a20 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -8687,12 +8687,8 @@ func (q *sqlQuerier) GetTemplatePresetsWithPrebuilds(ctx context.Context, templa } const updatePrebuildProvisionerJobWithCancel = `-- name: UpdatePrebuildProvisionerJobWithCancel :many -UPDATE provisioner_jobs -SET - canceled_at = $1::timestamptz, - completed_at = $1::timestamptz -WHERE id IN ( - SELECT pj.id +WITH jobs_to_cancel AS ( + SELECT pj.id, w.id AS workspace_id, w.template_id, wpb.template_version_preset_id FROM provisioner_jobs pj INNER JOIN workspace_prebuild_builds wpb ON wpb.job_id = pj.id INNER JOIN workspaces w ON w.id = wpb.workspace_id @@ -8711,7 +8707,13 @@ WHERE id IN ( AND pj.canceled_at IS NULL AND pj.completed_at IS NULL ) -RETURNING id +UPDATE provisioner_jobs +SET + canceled_at = $1::timestamptz, + completed_at = $1::timestamptz +FROM jobs_to_cancel +WHERE provisioner_jobs.id = jobs_to_cancel.id +RETURNING jobs_to_cancel.id, jobs_to_cancel.workspace_id, jobs_to_cancel.template_id, jobs_to_cancel.template_version_preset_id ` type UpdatePrebuildProvisionerJobWithCancelParams struct { @@ -8719,22 +8721,34 @@ type UpdatePrebuildProvisionerJobWithCancelParams struct { PresetID uuid.NullUUID `db:"preset_id" json:"preset_id"` } +type UpdatePrebuildProvisionerJobWithCancelRow struct { + ID uuid.UUID `db:"id" json:"id"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"` +} + // Cancels all pending provisioner jobs for prebuilt workspaces on a specific preset from an // inactive template version. // This is an optimization to clean up stale pending jobs. -func (q *sqlQuerier) UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg UpdatePrebuildProvisionerJobWithCancelParams) ([]uuid.UUID, error) { +func (q *sqlQuerier) UpdatePrebuildProvisionerJobWithCancel(ctx context.Context, arg UpdatePrebuildProvisionerJobWithCancelParams) ([]UpdatePrebuildProvisionerJobWithCancelRow, error) { rows, err := q.db.QueryContext(ctx, updatePrebuildProvisionerJobWithCancel, arg.Now, arg.PresetID) if err != nil { return nil, err } defer rows.Close() - var items []uuid.UUID + var items []UpdatePrebuildProvisionerJobWithCancelRow for rows.Next() { - var id uuid.UUID - if err := rows.Scan(&id); err != nil { + var i UpdatePrebuildProvisionerJobWithCancelRow + if err := rows.Scan( + &i.ID, + &i.WorkspaceID, + &i.TemplateID, + &i.TemplateVersionPresetID, + ); err != nil { return nil, err } - items = append(items, id) + items = append(items, i) } if err := rows.Close(); err != nil { return nil, err diff --git a/coderd/database/queries/prebuilds.sql b/coderd/database/queries/prebuilds.sql index 6c5520c9da7e1..7c060971efba5 100644 --- a/coderd/database/queries/prebuilds.sql +++ b/coderd/database/queries/prebuilds.sql @@ -300,12 +300,8 @@ GROUP BY wpb.template_version_preset_id; -- Cancels all pending provisioner jobs for prebuilt workspaces on a specific preset from an -- inactive template version. -- This is an optimization to clean up stale pending jobs. -UPDATE provisioner_jobs -SET - canceled_at = @now::timestamptz, - completed_at = @now::timestamptz -WHERE id IN ( - SELECT pj.id +WITH jobs_to_cancel AS ( + SELECT pj.id, w.id AS workspace_id, w.template_id, wpb.template_version_preset_id FROM provisioner_jobs pj INNER JOIN workspace_prebuild_builds wpb ON wpb.job_id = pj.id INNER JOIN workspaces w ON w.id = wpb.workspace_id @@ -324,4 +320,10 @@ WHERE id IN ( AND pj.canceled_at IS NULL AND pj.completed_at IS NULL ) -RETURNING id; +UPDATE provisioner_jobs +SET + canceled_at = @now::timestamptz, + completed_at = @now::timestamptz +FROM jobs_to_cancel +WHERE provisioner_jobs.id = jobs_to_cancel.id +RETURNING jobs_to_cancel.id, jobs_to_cancel.workspace_id, jobs_to_cancel.template_id, jobs_to_cancel.template_version_preset_id; diff --git a/enterprise/coderd/prebuilds/reconcile.go b/enterprise/coderd/prebuilds/reconcile.go index ceb16061bd7a7..5e5eec68ab382 100644 --- a/enterprise/coderd/prebuilds/reconcile.go +++ b/enterprise/coderd/prebuilds/reconcile.go @@ -57,6 +57,24 @@ type StoreReconciler struct { var _ prebuilds.ReconciliationOrchestrator = &StoreReconciler{} +type DeprovisionMode int + +const ( + DeprovisionModeNormal DeprovisionMode = iota + DeprovisionModeOrphan +) + +func (d DeprovisionMode) String() string { + switch d { + case DeprovisionModeOrphan: + return "orphan" + case DeprovisionModeNormal: + return "normal" + default: + return "unknown" + } +} + func NewStoreReconciler(store database.Store, ps pubsub.Pubsub, fileCache *files.Cache, @@ -642,34 +660,7 @@ func (c *StoreReconciler) executeReconciliationAction(ctx context.Context, logge return multiErr.ErrorOrNil() case prebuilds.ActionTypeCancelPending: - // Cancel pending prebuild jobs from non-active template versions to avoid - // provisioning obsolete workspaces that would immediately be deprovisioned. - // This uses a criteria-based update to ensure only jobs that are still pending - // at execution time are canceled, avoiding race conditions where jobs may have - // transitioned to running status between query and update. - canceledJobs, err := c.store.UpdatePrebuildProvisionerJobWithCancel( - ctx, - database.UpdatePrebuildProvisionerJobWithCancelParams{ - Now: c.clock.Now(), - PresetID: uuid.NullUUID{ - UUID: ps.Preset.ID, - Valid: true, - }, - }) - if err != nil { - logger.Error(ctx, "failed to cancel pending prebuild jobs", - slog.F("template_version_id", ps.Preset.TemplateVersionID.String()), - slog.F("preset_id", ps.Preset.ID), - slog.Error(err)) - return err - } - if len(canceledJobs) > 0 { - logger.Info(ctx, "canceled pending prebuild jobs for inactive version", - slog.F("template_version_id", ps.Preset.TemplateVersionID.String()), - slog.F("preset_id", ps.Preset.ID), - slog.F("count", len(canceledJobs))) - } - return nil + return c.cancelAndOrphanDeletePendingPrebuilds(ctx, ps.Preset.TemplateID, ps.Preset.TemplateVersionID, ps.Preset.ID) default: return xerrors.Errorf("unknown action type: %v", action.ActionType) @@ -717,33 +708,100 @@ func (c *StoreReconciler) createPrebuiltWorkspace(ctx context.Context, prebuiltW c.logger.Info(ctx, "attempting to create prebuild", slog.F("name", name), slog.F("workspace_id", prebuiltWorkspaceID.String()), slog.F("preset_id", presetID.String())) - return c.provision(ctx, db, prebuiltWorkspaceID, template, presetID, database.WorkspaceTransitionStart, workspace) + return c.provision(ctx, db, prebuiltWorkspaceID, template, presetID, database.WorkspaceTransitionStart, workspace, DeprovisionModeNormal) }, &database.TxOptions{ Isolation: sql.LevelRepeatableRead, ReadOnly: false, }) } -func (c *StoreReconciler) deletePrebuiltWorkspace(ctx context.Context, prebuiltWorkspaceID uuid.UUID, templateID uuid.UUID, presetID uuid.UUID) error { +// provisionDelete provisions a delete transition for a prebuilt workspace. +// +// If mode is DeprovisionModeOrphan, the builder will not send Terraform state to the provisioner. +// This allows the workspace to be deleted even when no provisioners are available, and is safe +// when no Terraform resources were actually created (e.g., for pending prebuilds that were canceled +// before provisioning started). +// +// IMPORTANT: This function must be called within a database transaction. It does not create its own transaction. +// The caller is responsible for managing the transaction boundary via db.InTx(). +func (c *StoreReconciler) provisionDelete(ctx context.Context, db database.Store, workspaceID uuid.UUID, templateID uuid.UUID, presetID uuid.UUID, mode DeprovisionMode) error { + workspace, err := db.GetWorkspaceByID(ctx, workspaceID) + if err != nil { + return xerrors.Errorf("get workspace by ID: %w", err) + } + + template, err := db.GetTemplateByID(ctx, templateID) + if err != nil { + return xerrors.Errorf("failed to get template: %w", err) + } + + if workspace.OwnerID != database.PrebuildsSystemUserID { + return xerrors.Errorf("prebuilt workspace is not owned by prebuild user anymore, probably it was claimed") + } + + c.logger.Info(ctx, "attempting to delete prebuild", slog.F("orphan", mode.String()), + slog.F("name", workspace.Name), slog.F("workspace_id", workspaceID.String()), slog.F("preset_id", presetID.String())) + + return c.provision(ctx, db, workspaceID, template, presetID, + database.WorkspaceTransitionDelete, workspace, mode) +} + +// cancelAndOrphanDeletePendingPrebuilds cancels pending prebuild jobs from inactive template versions +// and orphan-deletes their associated workspaces. +// +// The cancel operation uses a criteria-based update to ensure only jobs that are still pending at +// execution time are canceled, avoiding race conditions where jobs may have transitioned to running. +// +// Since these jobs were never processed by a provisioner, no Terraform resources were created, +// making it safe to orphan-delete the workspaces (skipping Terraform destroy). +func (c *StoreReconciler) cancelAndOrphanDeletePendingPrebuilds(ctx context.Context, templateID uuid.UUID, templateVersionID uuid.UUID, presetID uuid.UUID) error { return c.store.InTx(func(db database.Store) error { - workspace, err := db.GetWorkspaceByID(ctx, prebuiltWorkspaceID) + canceledJobs, err := db.UpdatePrebuildProvisionerJobWithCancel( + ctx, + database.UpdatePrebuildProvisionerJobWithCancelParams{ + Now: c.clock.Now(), + PresetID: uuid.NullUUID{ + UUID: presetID, + Valid: true, + }, + }) if err != nil { - return xerrors.Errorf("get workspace by ID: %w", err) + c.logger.Error(ctx, "failed to cancel pending prebuild jobs", + slog.F("template_id", templateID.String()), + slog.F("template_version_id", templateVersionID.String()), + slog.F("preset_id", presetID.String()), + slog.Error(err)) + return err } - template, err := db.GetTemplateByID(ctx, templateID) - if err != nil { - return xerrors.Errorf("failed to get template: %w", err) + if len(canceledJobs) > 0 { + c.logger.Info(ctx, "canceled pending prebuild jobs for inactive version", + slog.F("template_id", templateID.String()), + slog.F("template_version_id", templateVersionID.String()), + slog.F("preset_id", presetID.String()), + slog.F("count", len(canceledJobs))) } - if workspace.OwnerID != database.PrebuildsSystemUserID { - return xerrors.Errorf("prebuilt workspace is not owned by prebuild user anymore, probably it was claimed") + var multiErr multierror.Error + for _, job := range canceledJobs { + err = c.provisionDelete(ctx, db, job.WorkspaceID, job.TemplateID, presetID, DeprovisionModeOrphan) + if err != nil { + c.logger.Error(ctx, "failed to orphan delete canceled prebuild", + slog.F("workspace_id", job.WorkspaceID.String()), slog.Error(err)) + multiErr.Errors = append(multiErr.Errors, err) + } } - c.logger.Info(ctx, "attempting to delete prebuild", - slog.F("workspace_id", prebuiltWorkspaceID.String()), slog.F("preset_id", presetID.String())) + return multiErr.ErrorOrNil() + }, &database.TxOptions{ + Isolation: sql.LevelRepeatableRead, + ReadOnly: false, + }) +} - return c.provision(ctx, db, prebuiltWorkspaceID, template, presetID, database.WorkspaceTransitionDelete, workspace) +func (c *StoreReconciler) deletePrebuiltWorkspace(ctx context.Context, prebuiltWorkspaceID uuid.UUID, templateID uuid.UUID, presetID uuid.UUID) error { + return c.store.InTx(func(db database.Store) error { + return c.provisionDelete(ctx, db, prebuiltWorkspaceID, templateID, presetID, DeprovisionModeNormal) }, &database.TxOptions{ Isolation: sql.LevelRepeatableRead, ReadOnly: false, @@ -758,6 +816,7 @@ func (c *StoreReconciler) provision( presetID uuid.UUID, transition database.WorkspaceTransition, workspace database.Workspace, + mode DeprovisionMode, ) error { tvp, err := db.GetPresetParametersByTemplateVersionID(ctx, template.ActiveVersionID) if err != nil { @@ -795,6 +854,11 @@ func (c *StoreReconciler) provision( builder = builder.RichParameterValues(params) } + // Use orphan mode for deletes when no Terraform resources exist + if transition == database.WorkspaceTransitionDelete && mode == DeprovisionModeOrphan { + builder = builder.Orphan() + } + _, provisionerJob, _, err := builder.Build( ctx, db, diff --git a/enterprise/coderd/prebuilds/reconcile_test.go b/enterprise/coderd/prebuilds/reconcile_test.go index 33b99145d8e12..4b359ba9df429 100644 --- a/enterprise/coderd/prebuilds/reconcile_test.go +++ b/enterprise/coderd/prebuilds/reconcile_test.go @@ -204,7 +204,10 @@ func TestPrebuildReconciliation(t *testing.T) { templateDeleted: []bool{false}, }, { - name: "never attempt to interfere with active builds", + // TODO(ssncferreira): Investigate why the GetRunningPrebuiltWorkspaces query is returning 0 rows. + // When a template version is inactive (templateVersionActive = false), any prebuilds in the + // database.ProvisionerJobStatusRunning state should be deleted. + name: "never attempt to interfere with prebuilds from an active template version", // The workspace builder does not allow scheduling a new build if there is already a build // pending, running, or canceling. As such, we should never attempt to start, stop or delete // such prebuilds. Rather, we should wait for the existing build to complete and reconcile @@ -215,7 +218,7 @@ func TestPrebuildReconciliation(t *testing.T) { database.ProvisionerJobStatusRunning, database.ProvisionerJobStatusCanceling, }, - templateVersionActive: []bool{true, false}, + templateVersionActive: []bool{true}, shouldDeleteOldPrebuild: ptr.To(false), templateDeleted: []bool{false}, }, @@ -2121,16 +2124,16 @@ func TestCancelPendingPrebuilds(t *testing.T) { }, }).SkipCreateTemplate().Do() - var workspace dbfake.WorkspaceResponse + var pendingWorkspace dbfake.WorkspaceResponse if tt.activeTemplateVersion { // Given: a prebuilt workspace, workspace build and respective provisioner job from an // active template version - workspace = tt.setupBuild(t, db, client, + pendingWorkspace = tt.setupBuild(t, db, client, owner.OrganizationID, templateID, activeTemplateVersion.TemplateVersion.ID, activePresetID) } else { // Given: a prebuilt workspace, workspace build and respective provisioner job from a // non-active template version - workspace = tt.setupBuild(t, db, client, + pendingWorkspace = tt.setupBuild(t, db, client, owner.OrganizationID, templateID, nonActiveTemplateVersion.TemplateVersion.ID, nonActivePresetID) } @@ -2145,15 +2148,28 @@ func TestCancelPendingPrebuilds(t *testing.T) { require.NoError(t, reconciler.ReconcileAll(ctx)) if tt.shouldCancel { - // Then: the prebuild related jobs from non-active version should be canceled - cancelledJob, err := db.GetProvisionerJobByID(ctx, workspace.Build.JobID) + // Then: the pending prebuild job from non-active version should be canceled + cancelledJob, err := db.GetProvisionerJobByID(ctx, pendingWorkspace.Build.JobID) require.NoError(t, err) require.Equal(t, clock.Now().UTC(), cancelledJob.CanceledAt.Time.UTC()) require.Equal(t, clock.Now().UTC(), cancelledJob.CompletedAt.Time.UTC()) require.Equal(t, database.ProvisionerJobStatusCanceled, cancelledJob.JobStatus) + + // Then: the workspace should be deleted + deletedWorkspace, err := db.GetWorkspaceByID(ctx, pendingWorkspace.Workspace.ID) + require.NoError(t, err) + require.True(t, deletedWorkspace.Deleted) + latestBuild, err := db.GetLatestWorkspaceBuildByWorkspaceID(ctx, deletedWorkspace.ID) + require.NoError(t, err) + require.Equal(t, database.WorkspaceTransitionDelete, latestBuild.Transition) + deleteJob, err := db.GetProvisionerJobByID(ctx, latestBuild.JobID) + require.NoError(t, err) + require.True(t, deleteJob.CompletedAt.Valid) + require.False(t, deleteJob.WorkerID.Valid) + require.Equal(t, database.ProvisionerJobStatusSucceeded, deleteJob.JobStatus) } else { - // Then: the provisioner job should not be canceled - job, err := db.GetProvisionerJobByID(ctx, workspace.Build.JobID) + // Then: the pending prebuild job should not be canceled + job, err := db.GetProvisionerJobByID(ctx, pendingWorkspace.Build.JobID) require.NoError(t, err) if !tt.previouslyCanceled { require.Zero(t, job.CanceledAt.Time.UTC()) @@ -2162,6 +2178,11 @@ func TestCancelPendingPrebuilds(t *testing.T) { if !tt.previouslyCompleted { require.Zero(t, job.CompletedAt.Time.UTC()) } + + // Then: the workspace should not be deleted + workspace, err := db.GetWorkspaceByID(ctx, pendingWorkspace.Workspace.ID) + require.NoError(t, err) + require.False(t, workspace.Deleted) } }) } @@ -2235,25 +2256,45 @@ func TestCancelPendingPrebuilds(t *testing.T) { return prebuilds } - checkIfJobCanceled := func( + checkIfJobCanceledAndDeleted := func( t *testing.T, clock *quartz.Mock, ctx context.Context, db database.Store, - shouldBeCanceled bool, + shouldBeCanceledAndDeleted bool, prebuilds []dbfake.WorkspaceResponse, ) { for _, prebuild := range prebuilds { - job, err := db.GetProvisionerJobByID(ctx, prebuild.Build.JobID) + pendingJob, err := db.GetProvisionerJobByID(ctx, prebuild.Build.JobID) require.NoError(t, err) - if shouldBeCanceled { - require.Equal(t, database.ProvisionerJobStatusCanceled, job.JobStatus) - require.Equal(t, clock.Now().UTC(), job.CanceledAt.Time.UTC()) - require.Equal(t, clock.Now().UTC(), job.CompletedAt.Time.UTC()) + if shouldBeCanceledAndDeleted { + // Pending job should be canceled + require.Equal(t, database.ProvisionerJobStatusCanceled, pendingJob.JobStatus) + require.Equal(t, clock.Now().UTC(), pendingJob.CanceledAt.Time.UTC()) + require.Equal(t, clock.Now().UTC(), pendingJob.CompletedAt.Time.UTC()) + + // Workspace should be deleted + deletedWorkspace, err := db.GetWorkspaceByID(ctx, prebuild.Workspace.ID) + require.NoError(t, err) + require.True(t, deletedWorkspace.Deleted) + latestBuild, err := db.GetLatestWorkspaceBuildByWorkspaceID(ctx, deletedWorkspace.ID) + require.NoError(t, err) + require.Equal(t, database.WorkspaceTransitionDelete, latestBuild.Transition) + deleteJob, err := db.GetProvisionerJobByID(ctx, latestBuild.JobID) + require.NoError(t, err) + require.True(t, deleteJob.CompletedAt.Valid) + require.False(t, deleteJob.WorkerID.Valid) + require.Equal(t, database.ProvisionerJobStatusSucceeded, deleteJob.JobStatus) } else { - require.NotEqual(t, database.ProvisionerJobStatusCanceled, job.JobStatus) - require.Zero(t, job.CanceledAt.Time.UTC()) + // Pending job should not be canceled + require.NotEqual(t, database.ProvisionerJobStatusCanceled, pendingJob.JobStatus) + require.Zero(t, pendingJob.CanceledAt.Time.UTC()) + + // Workspace should not be deleted + workspace, err := db.GetWorkspaceByID(ctx, prebuild.Workspace.ID) + require.NoError(t, err) + require.False(t, workspace.Deleted) } } } @@ -2309,22 +2350,22 @@ func TestCancelPendingPrebuilds(t *testing.T) { require.NoError(t, reconciler.ReconcileAll(ctx)) // Then: template A version 1 running workspaces should not be canceled - checkIfJobCanceled(t, clock, ctx, db, false, templateAVersion1Running) + checkIfJobCanceledAndDeleted(t, clock, ctx, db, false, templateAVersion1Running) // Then: template A version 1 pending workspaces should be canceled - checkIfJobCanceled(t, clock, ctx, db, true, templateAVersion1Pending) + checkIfJobCanceledAndDeleted(t, clock, ctx, db, true, templateAVersion1Pending) // Then: template A version 2 running and pending workspaces should not be canceled - checkIfJobCanceled(t, clock, ctx, db, false, templateAVersion2Running) - checkIfJobCanceled(t, clock, ctx, db, false, templateAVersion2Pending) + checkIfJobCanceledAndDeleted(t, clock, ctx, db, false, templateAVersion2Running) + checkIfJobCanceledAndDeleted(t, clock, ctx, db, false, templateAVersion2Pending) // Then: template B version 1 running workspaces should not be canceled - checkIfJobCanceled(t, clock, ctx, db, false, templateBVersion1Running) + checkIfJobCanceledAndDeleted(t, clock, ctx, db, false, templateBVersion1Running) // Then: template B version 1 pending workspaces should be canceled - checkIfJobCanceled(t, clock, ctx, db, true, templateBVersion1Pending) + checkIfJobCanceledAndDeleted(t, clock, ctx, db, true, templateBVersion1Pending) // Then: template B version 2 pending workspaces should be canceled - checkIfJobCanceled(t, clock, ctx, db, true, templateBVersion2Pending) + checkIfJobCanceledAndDeleted(t, clock, ctx, db, true, templateBVersion2Pending) // Then: template B version 3 running and pending workspaces should not be canceled - checkIfJobCanceled(t, clock, ctx, db, false, templateBVersion3Running) - checkIfJobCanceled(t, clock, ctx, db, false, templateBVersion3Pending) + checkIfJobCanceledAndDeleted(t, clock, ctx, db, false, templateBVersion3Running) + checkIfJobCanceledAndDeleted(t, clock, ctx, db, false, templateBVersion3Pending) }) } From 95a1ca898f6ff885cdb9b9eb33556f625851cc20 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 29 Oct 2025 06:18:38 -0600 Subject: [PATCH 003/255] chore: remove aibridge experiment (#20520) Removes the experiment and all references to it --- cli/testdata/coder_server_--help.golden | 35 ++++++ cli/testdata/server-config.yaml.golden | 3 +- coderd/apidoc/docs.go | 7 +- coderd/apidoc/swagger.json | 7 +- codersdk/deployment.go | 16 +-- docs/reference/api/schemas.md | 1 - docs/reference/cli/server.md | 105 ++++++++++++++++++ enterprise/cli/exp_aibridge_test.go | 3 - enterprise/cli/server.go | 32 ++---- .../cli/testdata/coder_server_--help.golden | 35 ++++++ enterprise/coderd/aibridge_test.go | 7 -- enterprise/coderd/coderd.go | 5 +- site/src/api/typesGenerated.ts | 2 - 13 files changed, 192 insertions(+), 66 deletions(-) diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 447ce1ae4fce2..7e7a7ece0d958 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -80,6 +80,41 @@ OPTIONS: Periodically check for new releases of Coder and inform the owner. The check is performed once per day. +AIBRIDGE OPTIONS: + --aibridge-anthropic-base-url string, $CODER_AIBRIDGE_ANTHROPIC_BASE_URL (default: https://api.anthropic.com/) + The base URL of the Anthropic API. + + --aibridge-anthropic-key string, $CODER_AIBRIDGE_ANTHROPIC_KEY + The key to authenticate against the Anthropic API. + + --aibridge-bedrock-access-key string, $CODER_AIBRIDGE_BEDROCK_ACCESS_KEY + The access key to authenticate against the AWS Bedrock API. + + --aibridge-bedrock-access-key-secret string, $CODER_AIBRIDGE_BEDROCK_ACCESS_KEY_SECRET + The access key secret to use with the access key to authenticate + against the AWS Bedrock API. + + --aibridge-bedrock-model string, $CODER_AIBRIDGE_BEDROCK_MODEL (default: global.anthropic.claude-sonnet-4-5-20250929-v1:0) + The model to use when making requests to the AWS Bedrock API. + + --aibridge-bedrock-region string, $CODER_AIBRIDGE_BEDROCK_REGION + The AWS Bedrock API region. + + --aibridge-bedrock-small-fastmodel string, $CODER_AIBRIDGE_BEDROCK_SMALL_FAST_MODEL (default: global.anthropic.claude-haiku-4-5-20251001-v1:0) + The small fast model to use when making requests to the AWS Bedrock + API. Claude Code uses Haiku-class models to perform background tasks. + See + https://docs.claude.com/en/docs/claude-code/settings#environment-variables. + + --aibridge-enabled bool, $CODER_AIBRIDGE_ENABLED (default: false) + Whether to start an in-memory aibridged instance. + + --aibridge-openai-base-url string, $CODER_AIBRIDGE_OPENAI_BASE_URL (default: https://api.openai.com/v1/) + The base URL of the OpenAI API. + + --aibridge-openai-key string, $CODER_AIBRIDGE_OPENAI_KEY + The key to authenticate against the OpenAI API. + CLIENT OPTIONS: These options change the behavior of how clients interact with the Coder. Clients include the Coder CLI, Coder Desktop, IDE extensions, and the web UI. diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index cbabf0474f291..225c240d9e761 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -714,8 +714,7 @@ workspace_prebuilds: # (default: 3, type: int) failure_hard_limit: 3 aibridge: - # Whether to start an in-memory aibridged instance ("aibridge" experiment must be - # enabled, too). + # Whether to start an in-memory aibridged instance. # (default: false, type: bool) enabled: false # The base URL of the OpenAI API. diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 45b220eb9d255..89af9e2386180 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -14316,11 +14316,9 @@ const docTemplate = `{ "web-push", "oauth2", "mcp-server-http", - "workspace-sharing", - "aibridge" + "workspace-sharing" ], "x-enum-comments": { - "ExperimentAIBridge": "Enables AI Bridge functionality.", "ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.", "ExperimentExample": "This isn't used for anything.", "ExperimentMCPServerHTTP": "Enables the MCP HTTP server functionality.", @@ -14338,8 +14336,7 @@ const docTemplate = `{ "ExperimentWebPush", "ExperimentOAuth2", "ExperimentMCPServerHTTP", - "ExperimentWorkspaceSharing", - "ExperimentAIBridge" + "ExperimentWorkspaceSharing" ] }, "codersdk.ExternalAPIKeyScopes": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 8e6a0030cb83b..759647d9b1ba7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -12923,11 +12923,9 @@ "web-push", "oauth2", "mcp-server-http", - "workspace-sharing", - "aibridge" + "workspace-sharing" ], "x-enum-comments": { - "ExperimentAIBridge": "Enables AI Bridge functionality.", "ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.", "ExperimentExample": "This isn't used for anything.", "ExperimentMCPServerHTTP": "Enables the MCP HTTP server functionality.", @@ -12945,8 +12943,7 @@ "ExperimentWebPush", "ExperimentOAuth2", "ExperimentMCPServerHTTP", - "ExperimentWorkspaceSharing", - "ExperimentAIBridge" + "ExperimentWorkspaceSharing" ] }, "codersdk.ExternalAPIKeyScopes": { diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 97bbb13bedbc7..9425a3740f089 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -3241,14 +3241,13 @@ Write out the current server config as YAML to stdout.`, // AIBridge Options { Name: "AIBridge Enabled", - Description: fmt.Sprintf("Whether to start an in-memory aibridged instance (%q experiment must be enabled, too).", ExperimentAIBridge), + Description: "Whether to start an in-memory aibridged instance.", Flag: "aibridge-enabled", Env: "CODER_AIBRIDGE_ENABLED", Value: &c.AI.BridgeConfig.Enabled, Default: "false", Group: &deploymentGroupAIBridge, YAML: "enabled", - Hidden: true, }, { Name: "AIBridge OpenAI Base URL", @@ -3259,7 +3258,6 @@ Write out the current server config as YAML to stdout.`, Default: "https://api.openai.com/v1/", Group: &deploymentGroupAIBridge, YAML: "openai_base_url", - Hidden: true, }, { Name: "AIBridge OpenAI Key", @@ -3270,7 +3268,6 @@ Write out the current server config as YAML to stdout.`, Default: "", Group: &deploymentGroupAIBridge, YAML: "openai_key", - Hidden: true, }, { Name: "AIBridge Anthropic Base URL", @@ -3281,7 +3278,6 @@ Write out the current server config as YAML to stdout.`, Default: "https://api.anthropic.com/", Group: &deploymentGroupAIBridge, YAML: "anthropic_base_url", - Hidden: true, }, { Name: "AIBridge Anthropic Key", @@ -3292,7 +3288,6 @@ Write out the current server config as YAML to stdout.`, Default: "", Group: &deploymentGroupAIBridge, YAML: "anthropic_key", - Hidden: true, }, { Name: "AIBridge Bedrock Region", @@ -3303,7 +3298,6 @@ Write out the current server config as YAML to stdout.`, Default: "", Group: &deploymentGroupAIBridge, YAML: "bedrock_region", - Hidden: true, }, { Name: "AIBridge Bedrock Access Key", @@ -3314,7 +3308,6 @@ Write out the current server config as YAML to stdout.`, Default: "", Group: &deploymentGroupAIBridge, YAML: "bedrock_access_key", - Hidden: true, }, { Name: "AIBridge Bedrock Access Key Secret", @@ -3325,7 +3318,6 @@ Write out the current server config as YAML to stdout.`, Default: "", Group: &deploymentGroupAIBridge, YAML: "bedrock_access_key_secret", - Hidden: true, }, { Name: "AIBridge Bedrock Model", @@ -3336,7 +3328,6 @@ Write out the current server config as YAML to stdout.`, Default: "global.anthropic.claude-sonnet-4-5-20250929-v1:0", // See https://docs.claude.com/en/api/claude-on-amazon-bedrock#accessing-bedrock. Group: &deploymentGroupAIBridge, YAML: "bedrock_model", - Hidden: true, }, { Name: "AIBridge Bedrock Small Fast Model", @@ -3347,7 +3338,6 @@ Write out the current server config as YAML to stdout.`, Default: "global.anthropic.claude-haiku-4-5-20251001-v1:0", // See https://docs.claude.com/en/api/claude-on-amazon-bedrock#accessing-bedrock. Group: &deploymentGroupAIBridge, YAML: "bedrock_small_fast_model", - Hidden: true, }, { Name: "Enable Authorization Recordings", @@ -3645,7 +3635,6 @@ const ( ExperimentOAuth2 Experiment = "oauth2" // Enables OAuth2 provider functionality. ExperimentMCPServerHTTP Experiment = "mcp-server-http" // Enables the MCP HTTP server functionality. ExperimentWorkspaceSharing Experiment = "workspace-sharing" // Enables updating workspace ACLs for sharing with users and groups. - ExperimentAIBridge Experiment = "aibridge" // Enables AI Bridge functionality. ) func (e Experiment) DisplayName() string { @@ -3666,8 +3655,6 @@ func (e Experiment) DisplayName() string { return "MCP HTTP Server Functionality" case ExperimentWorkspaceSharing: return "Workspace Sharing" - case ExperimentAIBridge: - return "AI Bridge" default: // Split on hyphen and convert to title case // e.g. "web-push" -> "Web Push", "mcp-server-http" -> "Mcp Server Http" @@ -3686,7 +3673,6 @@ var ExperimentsKnown = Experiments{ ExperimentOAuth2, ExperimentMCPServerHTTP, ExperimentWorkspaceSharing, - ExperimentAIBridge, } // ExperimentsSafe should include all experiments that are safe for diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 796b4811cc4c1..037c9cfa109bb 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -4059,7 +4059,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `oauth2` | | `mcp-server-http` | | `workspace-sharing` | -| `aibridge` | ## codersdk.ExternalAPIKeyScopes diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md index bdc424bdd7a8b..e689f7fa28336 100644 --- a/docs/reference/cli/server.md +++ b/docs/reference/cli/server.md @@ -1647,3 +1647,108 @@ How often to reconcile workspace prebuilds state. | Default | false | Hide AI tasks from the dashboard. + +### --aibridge-enabled + +| | | +|-------------|--------------------------------------| +| Type | bool | +| Environment | $CODER_AIBRIDGE_ENABLED | +| YAML | aibridge.enabled | +| Default | false | + +Whether to start an in-memory aibridged instance. + +### --aibridge-openai-base-url + +| | | +|-------------|----------------------------------------------| +| Type | string | +| Environment | $CODER_AIBRIDGE_OPENAI_BASE_URL | +| YAML | aibridge.openai_base_url | +| Default | https://api.openai.com/v1/ | + +The base URL of the OpenAI API. + +### --aibridge-openai-key + +| | | +|-------------|-----------------------------------------| +| Type | string | +| Environment | $CODER_AIBRIDGE_OPENAI_KEY | +| YAML | aibridge.openai_key | + +The key to authenticate against the OpenAI API. + +### --aibridge-anthropic-base-url + +| | | +|-------------|-------------------------------------------------| +| Type | string | +| Environment | $CODER_AIBRIDGE_ANTHROPIC_BASE_URL | +| YAML | aibridge.anthropic_base_url | +| Default | https://api.anthropic.com/ | + +The base URL of the Anthropic API. + +### --aibridge-anthropic-key + +| | | +|-------------|--------------------------------------------| +| Type | string | +| Environment | $CODER_AIBRIDGE_ANTHROPIC_KEY | +| YAML | aibridge.anthropic_key | + +The key to authenticate against the Anthropic API. + +### --aibridge-bedrock-region + +| | | +|-------------|---------------------------------------------| +| Type | string | +| Environment | $CODER_AIBRIDGE_BEDROCK_REGION | +| YAML | aibridge.bedrock_region | + +The AWS Bedrock API region. + +### --aibridge-bedrock-access-key + +| | | +|-------------|-------------------------------------------------| +| Type | string | +| Environment | $CODER_AIBRIDGE_BEDROCK_ACCESS_KEY | +| YAML | aibridge.bedrock_access_key | + +The access key to authenticate against the AWS Bedrock API. + +### --aibridge-bedrock-access-key-secret + +| | | +|-------------|--------------------------------------------------------| +| Type | string | +| Environment | $CODER_AIBRIDGE_BEDROCK_ACCESS_KEY_SECRET | +| YAML | aibridge.bedrock_access_key_secret | + +The access key secret to use with the access key to authenticate against the AWS Bedrock API. + +### --aibridge-bedrock-model + +| | | +|-------------|---------------------------------------------------------------| +| Type | string | +| Environment | $CODER_AIBRIDGE_BEDROCK_MODEL | +| YAML | aibridge.bedrock_model | +| Default | global.anthropic.claude-sonnet-4-5-20250929-v1:0 | + +The model to use when making requests to the AWS Bedrock API. + +### --aibridge-bedrock-small-fastmodel + +| | | +|-------------|--------------------------------------------------------------| +| Type | string | +| Environment | $CODER_AIBRIDGE_BEDROCK_SMALL_FAST_MODEL | +| YAML | aibridge.bedrock_small_fast_model | +| Default | global.anthropic.claude-haiku-4-5-20251001-v1:0 | + +The small fast model to use when making requests to the AWS Bedrock API. Claude Code uses Haiku-class models to perform background tasks. See https://docs.claude.com/en/docs/claude-code/settings#environment-variables. diff --git a/enterprise/cli/exp_aibridge_test.go b/enterprise/cli/exp_aibridge_test.go index 466d6b3df8246..db1cf43629626 100644 --- a/enterprise/cli/exp_aibridge_test.go +++ b/enterprise/cli/exp_aibridge_test.go @@ -27,7 +27,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, @@ -78,7 +77,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, @@ -164,7 +162,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, diff --git a/enterprise/cli/server.go b/enterprise/cli/server.go index ea9f2d3e93825..9dab05546f6c6 100644 --- a/enterprise/cli/server.go +++ b/enterprise/cli/server.go @@ -7,7 +7,6 @@ import ( "database/sql" "encoding/base64" "errors" - "fmt" "io" "net/url" @@ -16,7 +15,6 @@ import ( "tailscale.com/types/key" "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/enterprise/audit" "github.com/coder/coder/v2/enterprise/audit/backends" @@ -146,8 +144,6 @@ func (r *RootCmd) Server(_ func()) *serpent.Command { } closers.Add(publisher) - experiments := agplcoderd.ReadExperiments(options.Logger, options.DeploymentValues.Experiments.Value()) - // In-memory aibridge daemon. // TODO(@deansheather): the lifecycle of the aibridged server is // probably better managed by the enterprise API type itself. Managing @@ -155,26 +151,18 @@ func (r *RootCmd) Server(_ func()) *serpent.Command { // is not entitled to the feature. var aibridgeDaemon *aibridged.Server if options.DeploymentValues.AI.BridgeConfig.Enabled { - if experiments.Enabled(codersdk.ExperimentAIBridge) { - aibridgeDaemon, err = newAIBridgeDaemon(api) - if err != nil { - return nil, nil, xerrors.Errorf("create aibridged: %w", err) - } + aibridgeDaemon, err = newAIBridgeDaemon(api) + if err != nil { + return nil, nil, xerrors.Errorf("create aibridged: %w", err) + } - api.RegisterInMemoryAIBridgedHTTPHandler(aibridgeDaemon) + api.RegisterInMemoryAIBridgedHTTPHandler(aibridgeDaemon) - // When running as an in-memory daemon, the HTTP handler is wired into the - // coderd API and therefore is subject to its context. Calling Close() on - // aibridged will NOT affect in-flight requests but those will be closed once - // the API server is itself shutdown. - closers.Add(aibridgeDaemon) - } else { - api.Logger.Warn(ctx, fmt.Sprintf("CODER_AIBRIDGE_ENABLED=true but experiment %q not enabled", codersdk.ExperimentAIBridge)) - } - } else { - if experiments.Enabled(codersdk.ExperimentAIBridge) { - api.Logger.Warn(ctx, "aibridge experiment enabled but CODER_AIBRIDGE_ENABLED=false") - } + // When running as an in-memory daemon, the HTTP handler is wired into the + // coderd API and therefore is subject to its context. Calling Close() on + // aibridged will NOT affect in-flight requests but those will be closed once + // the API server is itself shutdown. + closers.Add(aibridgeDaemon) } return api.AGPL, closers, nil diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden index 162d4214ccc6a..492306c55882d 100644 --- a/enterprise/cli/testdata/coder_server_--help.golden +++ b/enterprise/cli/testdata/coder_server_--help.golden @@ -81,6 +81,41 @@ OPTIONS: Periodically check for new releases of Coder and inform the owner. The check is performed once per day. +AIBRIDGE OPTIONS: + --aibridge-anthropic-base-url string, $CODER_AIBRIDGE_ANTHROPIC_BASE_URL (default: https://api.anthropic.com/) + The base URL of the Anthropic API. + + --aibridge-anthropic-key string, $CODER_AIBRIDGE_ANTHROPIC_KEY + The key to authenticate against the Anthropic API. + + --aibridge-bedrock-access-key string, $CODER_AIBRIDGE_BEDROCK_ACCESS_KEY + The access key to authenticate against the AWS Bedrock API. + + --aibridge-bedrock-access-key-secret string, $CODER_AIBRIDGE_BEDROCK_ACCESS_KEY_SECRET + The access key secret to use with the access key to authenticate + against the AWS Bedrock API. + + --aibridge-bedrock-model string, $CODER_AIBRIDGE_BEDROCK_MODEL (default: global.anthropic.claude-sonnet-4-5-20250929-v1:0) + The model to use when making requests to the AWS Bedrock API. + + --aibridge-bedrock-region string, $CODER_AIBRIDGE_BEDROCK_REGION + The AWS Bedrock API region. + + --aibridge-bedrock-small-fastmodel string, $CODER_AIBRIDGE_BEDROCK_SMALL_FAST_MODEL (default: global.anthropic.claude-haiku-4-5-20251001-v1:0) + The small fast model to use when making requests to the AWS Bedrock + API. Claude Code uses Haiku-class models to perform background tasks. + See + https://docs.claude.com/en/docs/claude-code/settings#environment-variables. + + --aibridge-enabled bool, $CODER_AIBRIDGE_ENABLED (default: false) + Whether to start an in-memory aibridged instance. + + --aibridge-openai-base-url string, $CODER_AIBRIDGE_OPENAI_BASE_URL (default: https://api.openai.com/v1/) + The base URL of the OpenAI API. + + --aibridge-openai-key string, $CODER_AIBRIDGE_OPENAI_KEY + The key to authenticate against the OpenAI API. + CLIENT OPTIONS: These options change the behavior of how clients interact with the Coder. Clients include the Coder CLI, Coder Desktop, IDE extensions, and the web UI. diff --git a/enterprise/coderd/aibridge_test.go b/enterprise/coderd/aibridge_test.go index abaf82dbe85f8..4198fdcf178ec 100644 --- a/enterprise/coderd/aibridge_test.go +++ b/enterprise/coderd/aibridge_test.go @@ -27,7 +27,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} client, _ := coderdenttest.New(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, @@ -50,7 +49,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Run("EmptyDB", func(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} client, _ := coderdenttest.New(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, @@ -71,7 +69,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Run("OK", func(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} client, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, @@ -183,7 +180,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} client, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, @@ -299,7 +295,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Run("Authorized", func(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} adminClient, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, @@ -345,7 +340,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Run("Filter", func(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} client, db, firstUser := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, @@ -526,7 +520,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Run("FilterErrors", func(t *testing.T) { t.Parallel() dv := coderdtest.DeploymentValues(t) - dv.Experiments = []string{string(codersdk.ExperimentAIBridge)} client, _ := coderdenttest.New(t, &coderdenttest.Options{ Options: &coderdtest.Options{ DeploymentValues: dv, diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 7666e8f957fc2..1bd517d0c86b1 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -228,10 +228,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { api.AGPL.ExperimentalHandler.Group(func(r chi.Router) { r.Route("/aibridge", func(r chi.Router) { - r.Use( - api.RequireFeatureMW(codersdk.FeatureAIBridge), - httpmw.RequireExperimentWithDevBypass(api.AGPL.Experiments, codersdk.ExperimentAIBridge), - ) + r.Use(api.RequireFeatureMW(codersdk.FeatureAIBridge)) r.Group(func(r chi.Router) { r.Use(apiKeyMiddleware) r.Get("/interceptions", api.aiBridgeListInterceptions) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index d671cc2bb117e..b964cf4d05f28 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1894,7 +1894,6 @@ export const EntitlementsWarningHeader = "X-Coder-Entitlements-Warning"; // From codersdk/deployment.go export type Experiment = - | "aibridge" | "auto-fill-parameters" | "example" | "mcp-server-http" @@ -1905,7 +1904,6 @@ export type Experiment = | "workspace-usage"; export const Experiments: Experiment[] = [ - "aibridge", "auto-fill-parameters", "example", "mcp-server-http", From a8294872a3f8348313af89a4e84324b1111a12ee Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 29 Oct 2025 07:44:09 -0500 Subject: [PATCH 004/255] chore: use consistent function for statefile path (#20527) We use `getStateFilePath` in 2 locations, and a manual `filepath.Join` here. Just refactored to use the helper function --- provisioner/terraform/executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index 5ef7f626f9f58..c7811fe2721dd 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -608,7 +608,7 @@ func (e *executor) apply( if err != nil { return nil, err } - statefilePath := filepath.Join(e.workdir, "terraform.tfstate") + statefilePath := getStateFilePath(e.workdir) stateContent, err := os.ReadFile(statefilePath) if err != nil { return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err) From aad1b401c12b287c5a33389db991e8e68c92c358 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Wed, 29 Oct 2025 12:52:30 +0000 Subject: [PATCH 005/255] feat: add prebuilds reconciliation duration metric (#20535) ## Description Adds `coderd_prebuilds_reconciliation_duration_seconds` histogram metric to track the duration of each prebuilds reconciliation cycle. This metric helps operators monitor reconciliation performance and identify potential bottlenecks. ## Changes - Added `ReconcileStats` struct to capture reconciliation cycle statistics - Updated `ReconcileAll()` to return stats including elapsed time - Added histogram metric `coderd_prebuilds_reconciliation_duration_seconds` --- coderd/prebuilds/api.go | 7 +- coderd/prebuilds/noop.go | 6 +- .../coderd/prebuilds/metricscollector_test.go | 6 +- enterprise/coderd/prebuilds/reconcile.go | 36 +++++-- enterprise/coderd/prebuilds/reconcile_test.go | 101 ++++++++++++++---- 5 files changed, 125 insertions(+), 31 deletions(-) diff --git a/coderd/prebuilds/api.go b/coderd/prebuilds/api.go index ed39f2a322776..0deab99416fd5 100644 --- a/coderd/prebuilds/api.go +++ b/coderd/prebuilds/api.go @@ -37,13 +37,18 @@ type ReconciliationOrchestrator interface { TrackResourceReplacement(ctx context.Context, workspaceID, buildID uuid.UUID, replacements []*sdkproto.ResourceReplacement) } +// ReconcileStats contains statistics about a reconciliation cycle. +type ReconcileStats struct { + Elapsed time.Duration +} + type Reconciler interface { StateSnapshotter // ReconcileAll orchestrates the reconciliation of all prebuilds across all templates. // It takes a global snapshot of the system state and then reconciles each preset // in parallel, creating or deleting prebuilds as needed to reach their desired states. - ReconcileAll(ctx context.Context) error + ReconcileAll(ctx context.Context) (ReconcileStats, error) } // StateSnapshotter defines the operations necessary to capture workspace prebuilds state. diff --git a/coderd/prebuilds/noop.go b/coderd/prebuilds/noop.go index 170b0a12af6fd..0859d428b4796 100644 --- a/coderd/prebuilds/noop.go +++ b/coderd/prebuilds/noop.go @@ -17,7 +17,11 @@ func (NoopReconciler) Run(context.Context) {} func (NoopReconciler) Stop(context.Context, error) {} func (NoopReconciler) TrackResourceReplacement(context.Context, uuid.UUID, uuid.UUID, []*sdkproto.ResourceReplacement) { } -func (NoopReconciler) ReconcileAll(context.Context) error { return nil } + +func (NoopReconciler) ReconcileAll(context.Context) (ReconcileStats, error) { + return ReconcileStats{}, nil +} + func (NoopReconciler) SnapshotState(context.Context, database.Store) (*GlobalSnapshot, error) { return &GlobalSnapshot{}, nil } diff --git a/enterprise/coderd/prebuilds/metricscollector_test.go b/enterprise/coderd/prebuilds/metricscollector_test.go index f9584e9ec2c25..aa9886fb7ad1b 100644 --- a/enterprise/coderd/prebuilds/metricscollector_test.go +++ b/enterprise/coderd/prebuilds/metricscollector_test.go @@ -485,7 +485,7 @@ func TestMetricsCollector_ReconciliationPausedMetric(t *testing.T) { require.NoError(t, err) // Run reconciliation to update the metric - err = reconciler.ReconcileAll(ctx) + _, err = reconciler.ReconcileAll(ctx) require.NoError(t, err) // Check that the metric shows reconciliation is not paused @@ -514,7 +514,7 @@ func TestMetricsCollector_ReconciliationPausedMetric(t *testing.T) { require.NoError(t, err) // Run reconciliation to update the metric - err = reconciler.ReconcileAll(ctx) + _, err = reconciler.ReconcileAll(ctx) require.NoError(t, err) // Check that the metric shows reconciliation is paused @@ -543,7 +543,7 @@ func TestMetricsCollector_ReconciliationPausedMetric(t *testing.T) { require.NoError(t, err) // Run reconciliation to update the metric - err = reconciler.ReconcileAll(ctx) + _, err = reconciler.ReconcileAll(ctx) require.NoError(t, err) // Check that the metric shows reconciliation is not paused diff --git a/enterprise/coderd/prebuilds/reconcile.go b/enterprise/coderd/prebuilds/reconcile.go index 5e5eec68ab382..9af8fbc4cd9d5 100644 --- a/enterprise/coderd/prebuilds/reconcile.go +++ b/enterprise/coderd/prebuilds/reconcile.go @@ -15,6 +15,7 @@ import ( "github.com/google/uuid" "github.com/hashicorp/go-multierror" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "golang.org/x/sync/errgroup" "golang.org/x/xerrors" @@ -44,7 +45,6 @@ type StoreReconciler struct { logger slog.Logger clock quartz.Clock registerer prometheus.Registerer - metrics *MetricsCollector notifEnq notifications.Enqueuer buildUsageChecker *atomic.Pointer[wsbuilder.UsageChecker] @@ -53,6 +53,11 @@ type StoreReconciler struct { stopped atomic.Bool done chan struct{} provisionNotifyCh chan database.ProvisionerJob + + // Prebuild state metrics + metrics *MetricsCollector + // Operational metrics + reconciliationDuration prometheus.Histogram } var _ prebuilds.ReconciliationOrchestrator = &StoreReconciler{} @@ -105,6 +110,15 @@ func NewStoreReconciler(store database.Store, // If the registerer fails to register the metrics collector, it's not fatal. logger.Error(context.Background(), "failed to register prometheus metrics", slog.Error(err)) } + + factory := promauto.With(registerer) + reconciler.reconciliationDuration = factory.NewHistogram(prometheus.HistogramOpts{ + Namespace: "coderd", + Subsystem: "prebuilds", + Name: "reconciliation_duration_seconds", + Help: "Duration of each prebuilds reconciliation cycle.", + Buckets: prometheus.DefBuckets, + }) } return reconciler @@ -176,10 +190,15 @@ func (c *StoreReconciler) Run(ctx context.Context) { // instead of waiting for the next reconciliation interval case <-ticker.C: // Trigger a new iteration on each tick. - err := c.ReconcileAll(ctx) + stats, err := c.ReconcileAll(ctx) if err != nil { c.logger.Error(context.Background(), "reconciliation failed", slog.Error(err)) } + + if c.reconciliationDuration != nil { + c.reconciliationDuration.Observe(stats.Elapsed.Seconds()) + } + c.logger.Debug(ctx, "reconciliation stats", slog.F("elapsed", stats.Elapsed)) case <-ctx.Done(): // nolint:gocritic // it's okay to use slog.F() for an error in this case // because we want to differentiate two different types of errors: ctx.Err() and context.Cause() @@ -263,19 +282,24 @@ func (c *StoreReconciler) Stop(ctx context.Context, cause error) { // be reconciled again, leading to another workspace being provisioned. Two workspace builds will be occurring // simultaneously for the same preset, but once both jobs have completed the reconciliation loop will notice the // extraneous instance and delete it. -func (c *StoreReconciler) ReconcileAll(ctx context.Context) error { +func (c *StoreReconciler) ReconcileAll(ctx context.Context) (stats prebuilds.ReconcileStats, err error) { + start := c.clock.Now() + defer func() { + stats.Elapsed = c.clock.Since(start) + }() + logger := c.logger.With(slog.F("reconcile_context", "all")) select { case <-ctx.Done(): logger.Warn(context.Background(), "reconcile exiting prematurely; context done", slog.Error(ctx.Err())) - return nil + return stats, nil default: } logger.Debug(ctx, "starting reconciliation") - err := c.WithReconciliationLock(ctx, logger, func(ctx context.Context, _ database.Store) error { + err = c.WithReconciliationLock(ctx, logger, func(ctx context.Context, _ database.Store) error { // Check if prebuilds reconciliation is paused settingsJSON, err := c.store.GetPrebuildsSettings(ctx) if err != nil { @@ -348,7 +372,7 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) error { logger.Error(ctx, "failed to reconcile", slog.Error(err)) } - return err + return stats, err } func (c *StoreReconciler) reportHardLimitedPresets(snapshot *prebuilds.GlobalSnapshot) { diff --git a/enterprise/coderd/prebuilds/reconcile_test.go b/enterprise/coderd/prebuilds/reconcile_test.go index 4b359ba9df429..7548faebd7dab 100644 --- a/enterprise/coderd/prebuilds/reconcile_test.go +++ b/enterprise/coderd/prebuilds/reconcile_test.go @@ -72,7 +72,8 @@ func TestNoReconciliationActionsIfNoPresets(t *testing.T) { require.Equal(t, templateVersion, gotTemplateVersion) // when we trigger the reconciliation loop for all templates - require.NoError(t, controller.ReconcileAll(ctx)) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) // then no reconciliation actions are taken // because without presets, there are no prebuilds @@ -126,7 +127,8 @@ func TestNoReconciliationActionsIfNoPrebuilds(t *testing.T) { require.NotEmpty(t, presetParameters) // when we trigger the reconciliation loop for all templates - require.NoError(t, controller.ReconcileAll(ctx)) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) // then no reconciliation actions are taken // because without prebuilds, there is nothing to reconcile @@ -428,7 +430,8 @@ func (tc testCase) run(t *testing.T) { // Run the reconciliation multiple times to ensure idempotency // 8 was arbitrary, but large enough to reasonably trust the result for i := 1; i <= 8; i++ { - require.NoErrorf(t, controller.ReconcileAll(ctx), "failed on iteration %d", i) + _, err := controller.ReconcileAll(ctx) + require.NoErrorf(t, err, "failed on iteration %d", i) if tc.shouldCreateNewPrebuild != nil { newPrebuildCount := 0 @@ -542,7 +545,8 @@ func TestMultiplePresetsPerTemplateVersion(t *testing.T) { // Run the reconciliation multiple times to ensure idempotency // 8 was arbitrary, but large enough to reasonably trust the result for i := 1; i <= 8; i++ { - require.NoErrorf(t, controller.ReconcileAll(ctx), "failed on iteration %d", i) + _, err := controller.ReconcileAll(ctx) + require.NoErrorf(t, err, "failed on iteration %d", i) newPrebuildCount := 0 workspaces, err := db.GetWorkspacesByTemplateID(ctx, template.ID) @@ -668,7 +672,7 @@ func TestPrebuildScheduling(t *testing.T) { DesiredInstances: 5, }) - err := controller.ReconcileAll(ctx) + _, err := controller.ReconcileAll(ctx) require.NoError(t, err) // get workspace builds @@ -751,7 +755,8 @@ func TestInvalidPreset(t *testing.T) { // Run the reconciliation multiple times to ensure idempotency // 8 was arbitrary, but large enough to reasonably trust the result for i := 1; i <= 8; i++ { - require.NoErrorf(t, controller.ReconcileAll(ctx), "failed on iteration %d", i) + _, err := controller.ReconcileAll(ctx) + require.NoErrorf(t, err, "failed on iteration %d", i) workspaces, err := db.GetWorkspacesByTemplateID(ctx, template.ID) require.NoError(t, err) @@ -817,7 +822,8 @@ func TestDeletionOfPrebuiltWorkspaceWithInvalidPreset(t *testing.T) { }) // Old prebuilt workspace should be deleted. - require.NoError(t, controller.ReconcileAll(ctx)) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) builds, err := db.GetWorkspaceBuildsByWorkspaceID(ctx, database.GetWorkspaceBuildsByWorkspaceIDParams{ WorkspaceID: prebuiltWorkspace.ID, @@ -916,12 +922,15 @@ func TestSkippingHardLimitedPresets(t *testing.T) { // Trigger reconciliation to attempt creating a new prebuild. // The outcome depends on whether the hard limit has been reached. - require.NoError(t, controller.ReconcileAll(ctx)) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) // These two additional calls to ReconcileAll should not trigger any notifications. // A notification is only sent once. - require.NoError(t, controller.ReconcileAll(ctx)) - require.NoError(t, controller.ReconcileAll(ctx)) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) // Verify the final state after reconciliation. workspaces, err = db.GetWorkspacesByTemplateID(ctx, template.ID) @@ -1093,12 +1102,15 @@ func TestHardLimitedPresetShouldNotBlockDeletion(t *testing.T) { // Trigger reconciliation to attempt creating a new prebuild. // The outcome depends on whether the hard limit has been reached. - require.NoError(t, controller.ReconcileAll(ctx)) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) // These two additional calls to ReconcileAll should not trigger any notifications. // A notification is only sent once. - require.NoError(t, controller.ReconcileAll(ctx)) - require.NoError(t, controller.ReconcileAll(ctx)) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) // Verify the final state after reconciliation. // When hard limit is reached, no new workspace should be created. @@ -1141,7 +1153,8 @@ func TestHardLimitedPresetShouldNotBlockDeletion(t *testing.T) { } // Trigger reconciliation to make sure that successful, but outdated prebuilt workspace will be deleted. - require.NoError(t, controller.ReconcileAll(ctx)) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) workspaces, err = db.GetWorkspacesByTemplateID(ctx, template.ID) require.NoError(t, err) @@ -1740,7 +1753,8 @@ func TestExpiredPrebuildsMultipleActions(t *testing.T) { } // Trigger reconciliation to process expired prebuilds and enforce desired state. - require.NoError(t, controller.ReconcileAll(ctx)) + _, err = controller.ReconcileAll(ctx) + require.NoError(t, err) // Sort non-expired workspaces by CreatedAt in ascending order (oldest first) sort.Slice(nonExpiredWorkspaces, func(i, j int) bool { @@ -2145,7 +2159,8 @@ func TestCancelPendingPrebuilds(t *testing.T) { require.NoError(t, err) // When: the reconciliation loop is triggered - require.NoError(t, reconciler.ReconcileAll(ctx)) + _, err = reconciler.ReconcileAll(ctx) + require.NoError(t, err) if tt.shouldCancel { // Then: the pending prebuild job from non-active version should be canceled @@ -2347,7 +2362,8 @@ func TestCancelPendingPrebuilds(t *testing.T) { templateBVersion3Pending := setupPrebuilds(t, db, owner.OrganizationID, templateBID, templateBVersion3ID, templateBVersion3PresetID, 1, true) // When: the reconciliation loop is executed - require.NoError(t, reconciler.ReconcileAll(ctx)) + _, err := reconciler.ReconcileAll(ctx) + require.NoError(t, err) // Then: template A version 1 running workspaces should not be canceled checkIfJobCanceledAndDeleted(t, clock, ctx, db, false, templateAVersion1Running) @@ -2369,6 +2385,51 @@ func TestCancelPendingPrebuilds(t *testing.T) { }) } +func TestReconciliationStats(t *testing.T) { + t.Parallel() + + // Setup + clock := quartz.NewReal() + db, ps := dbtestutil.NewDB(t) + client, _, _ := coderdtest.NewWithAPI(t, &coderdtest.Options{ + Database: db, + Pubsub: ps, + Clock: clock, + }) + fakeEnqueuer := newFakeEnqueuer() + registry := prometheus.NewRegistry() + cache := files.New(registry, &coderdtest.FakeAuthorizer{}) + logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug) + reconciler := prebuilds.NewStoreReconciler(db, ps, cache, codersdk.PrebuildsConfig{}, logger, clock, registry, fakeEnqueuer, newNoopUsageCheckerPtr()) + owner := coderdtest.CreateFirstUser(t, client) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) + defer cancel() + + // Create a template version with a preset + dbfake.TemplateVersion(t, db).Seed(database.TemplateVersion{ + OrganizationID: owner.OrganizationID, + CreatedBy: owner.UserID, + }).Preset(database.TemplateVersionPreset{ + DesiredInstances: sql.NullInt32{ + Int32: 1, + Valid: true, + }, + }).Do() + + // Verify that ReconcileAll tracks and returns elapsed time + start := time.Now() + stats, err := reconciler.ReconcileAll(ctx) + actualElapsed := time.Since(start) + require.NoError(t, err) + require.Greater(t, stats.Elapsed, time.Duration(0)) + + // Verify stats.Elapsed matches actual execution time + require.InDelta(t, actualElapsed.Milliseconds(), stats.Elapsed.Milliseconds(), 100) + // Verify reconciliation loop is not unexpectedly slow + require.Less(t, stats.Elapsed, 5*time.Second) +} + func newNoopEnqueuer() *notifications.NoopEnqueuer { return notifications.NewNoopEnqueuer() } @@ -2863,7 +2924,7 @@ func TestReconciliationRespectsPauseSetting(t *testing.T) { _ = setupTestDBPreset(t, db, templateVersionID, 2, "test") // Initially, reconciliation should create prebuilds - err := reconciler.ReconcileAll(ctx) + _, err := reconciler.ReconcileAll(ctx) require.NoError(t, err) // Verify that prebuilds were created @@ -2890,7 +2951,7 @@ func TestReconciliationRespectsPauseSetting(t *testing.T) { require.Len(t, workspaces, 0, "prebuilds should be deleted") // Run reconciliation again - it should be paused and not recreate prebuilds - err = reconciler.ReconcileAll(ctx) + _, err = reconciler.ReconcileAll(ctx) require.NoError(t, err) // Verify that no new prebuilds were created because reconciliation is paused @@ -2903,7 +2964,7 @@ func TestReconciliationRespectsPauseSetting(t *testing.T) { require.NoError(t, err) // Run reconciliation again - it should now recreate the prebuilds - err = reconciler.ReconcileAll(ctx) + _, err = reconciler.ReconcileAll(ctx) require.NoError(t, err) // Verify that prebuilds were recreated From 2294c55bd9211ee4e6e00f338efbc5ab6eb77d56 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 29 Oct 2025 07:00:24 -0600 Subject: [PATCH 006/255] chore: graduate `aibridged*` packages out of experimental (#20522) --- Makefile | 16 +- enterprise/{x => }/aibridged/aibridged.go | 0 .../aibridged/aibridged_integration_test.go | 2 +- .../{x => }/aibridged/aibridged_test.go | 6 +- .../aibridged/aibridgedmock/clientmock.go | 6 +- .../{x => }/aibridged/aibridgedmock/doc.go | 4 +- .../aibridged/aibridgedmock/poolmock.go | 6 +- enterprise/{x => }/aibridged/client.go | 2 +- enterprise/{x => }/aibridged/http.go | 2 +- enterprise/{x => }/aibridged/mcp.go | 2 +- .../{x => }/aibridged/mcp_internal_test.go | 2 +- enterprise/{x => }/aibridged/pool.go | 0 enterprise/{x => }/aibridged/pool_test.go | 4 +- .../{x => }/aibridged/proto/aibridged.pb.go | 627 +++++++++--------- .../{x => }/aibridged/proto/aibridged.proto | 0 .../aibridged/proto/aibridged_drpc.pb.go | 62 +- enterprise/{x => }/aibridged/request.go | 0 enterprise/{x => }/aibridged/server.go | 2 +- enterprise/{x => }/aibridged/translator.go | 2 +- enterprise/{x => }/aibridged/utils_test.go | 0 .../aibridgedserver/aibridgedserver.go | 4 +- .../aibridgedserver_internal_test.go | 0 .../aibridgedserver/aibridgedserver_test.go | 6 +- enterprise/cli/aibridged.go | 2 +- enterprise/cli/server.go | 2 +- enterprise/coderd/aibridged.go | 6 +- 26 files changed, 382 insertions(+), 383 deletions(-) rename enterprise/{x => }/aibridged/aibridged.go (100%) rename enterprise/{x => }/aibridged/aibridged_integration_test.go (99%) rename enterprise/{x => }/aibridged/aibridged_test.go (98%) rename enterprise/{x => }/aibridged/aibridgedmock/clientmock.go (97%) rename enterprise/{x => }/aibridged/aibridgedmock/doc.go (52%) rename enterprise/{x => }/aibridged/aibridgedmock/poolmock.go (91%) rename enterprise/{x => }/aibridged/client.go (90%) rename enterprise/{x => }/aibridged/http.go (98%) rename enterprise/{x => }/aibridged/mcp.go (99%) rename enterprise/{x => }/aibridged/mcp_internal_test.go (95%) rename enterprise/{x => }/aibridged/pool.go (100%) rename enterprise/{x => }/aibridged/pool_test.go (96%) rename enterprise/{x => }/aibridged/proto/aibridged.pb.go (60%) rename enterprise/{x => }/aibridged/proto/aibridged.proto (100%) rename enterprise/{x => }/aibridged/proto/aibridged_drpc.pb.go (84%) rename enterprise/{x => }/aibridged/request.go (100%) rename enterprise/{x => }/aibridged/server.go (68%) rename enterprise/{x => }/aibridged/translator.go (98%) rename enterprise/{x => }/aibridged/utils_test.go (100%) rename enterprise/{x => }/aibridgedserver/aibridgedserver.go (99%) rename enterprise/{x => }/aibridgedserver/aibridgedserver_internal_test.go (100%) rename enterprise/{x => }/aibridgedserver/aibridgedserver_test.go (99%) diff --git a/Makefile b/Makefile index 7f21f1fa6da04..7ecb64975e548 100644 --- a/Makefile +++ b/Makefile @@ -636,8 +636,8 @@ TAILNETTEST_MOCKS := \ tailnet/tailnettest/subscriptionmock.go AIBRIDGED_MOCKS := \ - enterprise/x/aibridged/aibridgedmock/clientmock.go \ - enterprise/x/aibridged/aibridgedmock/poolmock.go + enterprise/aibridged/aibridgedmock/clientmock.go \ + enterprise/aibridged/aibridgedmock/poolmock.go GEN_FILES := \ tailnet/proto/tailnet.pb.go \ @@ -645,7 +645,7 @@ GEN_FILES := \ provisionersdk/proto/provisioner.pb.go \ provisionerd/proto/provisionerd.pb.go \ vpn/vpn.pb.go \ - enterprise/x/aibridged/proto/aibridged.pb.go \ + enterprise/aibridged/proto/aibridged.pb.go \ $(DB_GEN_FILES) \ $(SITE_GEN_FILES) \ coderd/rbac/object_gen.go \ @@ -697,7 +697,7 @@ gen/mark-fresh: provisionersdk/proto/provisioner.pb.go \ provisionerd/proto/provisionerd.pb.go \ vpn/vpn.pb.go \ - enterprise/x/aibridged/proto/aibridged.pb.go \ + enterprise/aibridged/proto/aibridged.pb.go \ coderd/database/dump.sql \ $(DB_GEN_FILES) \ site/src/api/typesGenerated.ts \ @@ -768,8 +768,8 @@ codersdk/workspacesdk/agentconnmock/agentconnmock.go: codersdk/workspacesdk/agen go generate ./codersdk/workspacesdk/agentconnmock/ touch "$@" -$(AIBRIDGED_MOCKS): enterprise/x/aibridged/client.go enterprise/x/aibridged/pool.go - go generate ./enterprise/x/aibridged/aibridgedmock/ +$(AIBRIDGED_MOCKS): enterprise/aibridged/client.go enterprise/aibridged/pool.go + go generate ./enterprise/aibridged/aibridgedmock/ touch "$@" agent/agentcontainers/dcspec/dcspec_gen.go: \ @@ -822,13 +822,13 @@ vpn/vpn.pb.go: vpn/vpn.proto --go_opt=paths=source_relative \ ./vpn/vpn.proto -enterprise/x/aibridged/proto/aibridged.pb.go: enterprise/x/aibridged/proto/aibridged.proto +enterprise/aibridged/proto/aibridged.pb.go: enterprise/aibridged/proto/aibridged.proto protoc \ --go_out=. \ --go_opt=paths=source_relative \ --go-drpc_out=. \ --go-drpc_opt=paths=source_relative \ - ./enterprise/x/aibridged/proto/aibridged.proto + ./enterprise/aibridged/proto/aibridged.proto site/src/api/typesGenerated.ts: site/node_modules/.installed $(wildcard scripts/apitypings/*) $(shell find ./codersdk $(FIND_EXCLUSIONS) -type f -name '*.go') # -C sets the directory for the go run command diff --git a/enterprise/x/aibridged/aibridged.go b/enterprise/aibridged/aibridged.go similarity index 100% rename from enterprise/x/aibridged/aibridged.go rename to enterprise/aibridged/aibridged.go diff --git a/enterprise/x/aibridged/aibridged_integration_test.go b/enterprise/aibridged/aibridged_integration_test.go similarity index 99% rename from enterprise/x/aibridged/aibridged_integration_test.go rename to enterprise/aibridged/aibridged_integration_test.go index 45d47bd1b3507..88fa21377f5a2 100644 --- a/enterprise/x/aibridged/aibridged_integration_test.go +++ b/enterprise/aibridged/aibridged_integration_test.go @@ -19,8 +19,8 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/enterprise/aibridged" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" - "github.com/coder/coder/v2/enterprise/x/aibridged" "github.com/coder/coder/v2/testutil" ) diff --git a/enterprise/x/aibridged/aibridged_test.go b/enterprise/aibridged/aibridged_test.go similarity index 98% rename from enterprise/x/aibridged/aibridged_test.go rename to enterprise/aibridged/aibridged_test.go index 967e9aac2bce3..5d38b7f54d18c 100644 --- a/enterprise/x/aibridged/aibridged_test.go +++ b/enterprise/aibridged/aibridged_test.go @@ -18,9 +18,9 @@ import ( "cdr.dev/slog/sloggers/slogtest" "github.com/coder/aibridge" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/enterprise/x/aibridged" - mock "github.com/coder/coder/v2/enterprise/x/aibridged/aibridgedmock" - "github.com/coder/coder/v2/enterprise/x/aibridged/proto" + "github.com/coder/coder/v2/enterprise/aibridged" + mock "github.com/coder/coder/v2/enterprise/aibridged/aibridgedmock" + "github.com/coder/coder/v2/enterprise/aibridged/proto" "github.com/coder/coder/v2/testutil" ) diff --git a/enterprise/x/aibridged/aibridgedmock/clientmock.go b/enterprise/aibridged/aibridgedmock/clientmock.go similarity index 97% rename from enterprise/x/aibridged/aibridgedmock/clientmock.go rename to enterprise/aibridged/aibridgedmock/clientmock.go index c49a385451a8e..2bb7083e10924 100644 --- a/enterprise/x/aibridged/aibridgedmock/clientmock.go +++ b/enterprise/aibridged/aibridgedmock/clientmock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/coder/coder/v2/enterprise/x/aibridged (interfaces: DRPCClient) +// Source: github.com/coder/coder/v2/enterprise/aibridged (interfaces: DRPCClient) // // Generated by this command: // -// mockgen -destination ./clientmock.go -package aibridgedmock github.com/coder/coder/v2/enterprise/x/aibridged DRPCClient +// mockgen -destination ./clientmock.go -package aibridgedmock github.com/coder/coder/v2/enterprise/aibridged DRPCClient // // Package aibridgedmock is a generated GoMock package. @@ -13,7 +13,7 @@ import ( context "context" reflect "reflect" - proto "github.com/coder/coder/v2/enterprise/x/aibridged/proto" + proto "github.com/coder/coder/v2/enterprise/aibridged/proto" gomock "go.uber.org/mock/gomock" drpc "storj.io/drpc" ) diff --git a/enterprise/x/aibridged/aibridgedmock/doc.go b/enterprise/aibridged/aibridgedmock/doc.go similarity index 52% rename from enterprise/x/aibridged/aibridgedmock/doc.go rename to enterprise/aibridged/aibridgedmock/doc.go index 3d3f56c05574d..9c9c644570463 100644 --- a/enterprise/x/aibridged/aibridgedmock/doc.go +++ b/enterprise/aibridged/aibridgedmock/doc.go @@ -1,4 +1,4 @@ package aibridgedmock -//go:generate mockgen -destination ./clientmock.go -package aibridgedmock github.com/coder/coder/v2/enterprise/x/aibridged DRPCClient -//go:generate mockgen -destination ./poolmock.go -package aibridgedmock github.com/coder/coder/v2/enterprise/x/aibridged Pooler +//go:generate mockgen -destination ./clientmock.go -package aibridgedmock github.com/coder/coder/v2/enterprise/aibridged DRPCClient +//go:generate mockgen -destination ./poolmock.go -package aibridgedmock github.com/coder/coder/v2/enterprise/aibridged Pooler diff --git a/enterprise/x/aibridged/aibridgedmock/poolmock.go b/enterprise/aibridged/aibridgedmock/poolmock.go similarity index 91% rename from enterprise/x/aibridged/aibridgedmock/poolmock.go rename to enterprise/aibridged/aibridgedmock/poolmock.go index bf3b39ed2a879..fcd941fc7c989 100644 --- a/enterprise/x/aibridged/aibridgedmock/poolmock.go +++ b/enterprise/aibridged/aibridgedmock/poolmock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/coder/coder/v2/enterprise/x/aibridged (interfaces: Pooler) +// Source: github.com/coder/coder/v2/enterprise/aibridged (interfaces: Pooler) // // Generated by this command: // -// mockgen -destination ./poolmock.go -package aibridgedmock github.com/coder/coder/v2/enterprise/x/aibridged Pooler +// mockgen -destination ./poolmock.go -package aibridgedmock github.com/coder/coder/v2/enterprise/aibridged Pooler // // Package aibridgedmock is a generated GoMock package. @@ -14,7 +14,7 @@ import ( http "net/http" reflect "reflect" - aibridged "github.com/coder/coder/v2/enterprise/x/aibridged" + aibridged "github.com/coder/coder/v2/enterprise/aibridged" gomock "go.uber.org/mock/gomock" ) diff --git a/enterprise/x/aibridged/client.go b/enterprise/aibridged/client.go similarity index 90% rename from enterprise/x/aibridged/client.go rename to enterprise/aibridged/client.go index 3004a84df9626..60650bf994f28 100644 --- a/enterprise/x/aibridged/client.go +++ b/enterprise/aibridged/client.go @@ -5,7 +5,7 @@ import ( "storj.io/drpc" - "github.com/coder/coder/v2/enterprise/x/aibridged/proto" + "github.com/coder/coder/v2/enterprise/aibridged/proto" ) type Dialer func(ctx context.Context) (DRPCClient, error) diff --git a/enterprise/x/aibridged/http.go b/enterprise/aibridged/http.go similarity index 98% rename from enterprise/x/aibridged/http.go rename to enterprise/aibridged/http.go index 43f4ba7670671..e87238cc7bbc0 100644 --- a/enterprise/x/aibridged/http.go +++ b/enterprise/aibridged/http.go @@ -9,7 +9,7 @@ import ( "cdr.dev/slog" "github.com/coder/aibridge" - "github.com/coder/coder/v2/enterprise/x/aibridged/proto" + "github.com/coder/coder/v2/enterprise/aibridged/proto" ) var _ http.Handler = &Server{} diff --git a/enterprise/x/aibridged/mcp.go b/enterprise/aibridged/mcp.go similarity index 99% rename from enterprise/x/aibridged/mcp.go rename to enterprise/aibridged/mcp.go index 4b42287e02899..ab6d1d0031d37 100644 --- a/enterprise/x/aibridged/mcp.go +++ b/enterprise/aibridged/mcp.go @@ -10,7 +10,7 @@ import ( "cdr.dev/slog" "github.com/coder/aibridge/mcp" - "github.com/coder/coder/v2/enterprise/x/aibridged/proto" + "github.com/coder/coder/v2/enterprise/aibridged/proto" ) var ( diff --git a/enterprise/x/aibridged/mcp_internal_test.go b/enterprise/aibridged/mcp_internal_test.go similarity index 95% rename from enterprise/x/aibridged/mcp_internal_test.go rename to enterprise/aibridged/mcp_internal_test.go index 20edf79d06bf5..37fb6fe2c25d2 100644 --- a/enterprise/x/aibridged/mcp_internal_test.go +++ b/enterprise/aibridged/mcp_internal_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/coder/coder/v2/enterprise/x/aibridged/proto" + "github.com/coder/coder/v2/enterprise/aibridged/proto" "github.com/coder/coder/v2/testutil" ) diff --git a/enterprise/x/aibridged/pool.go b/enterprise/aibridged/pool.go similarity index 100% rename from enterprise/x/aibridged/pool.go rename to enterprise/aibridged/pool.go diff --git a/enterprise/x/aibridged/pool_test.go b/enterprise/aibridged/pool_test.go similarity index 96% rename from enterprise/x/aibridged/pool_test.go rename to enterprise/aibridged/pool_test.go index 38cae85da9d92..e3609144f0d59 100644 --- a/enterprise/x/aibridged/pool_test.go +++ b/enterprise/aibridged/pool_test.go @@ -13,8 +13,8 @@ import ( "cdr.dev/slog/sloggers/slogtest" "github.com/coder/aibridge/mcp" "github.com/coder/aibridge/mcpmock" - "github.com/coder/coder/v2/enterprise/x/aibridged" - mock "github.com/coder/coder/v2/enterprise/x/aibridged/aibridgedmock" + "github.com/coder/coder/v2/enterprise/aibridged" + mock "github.com/coder/coder/v2/enterprise/aibridged/aibridgedmock" ) // TestPool validates the published behavior of [aibridged.CachedBridgePool]. diff --git a/enterprise/x/aibridged/proto/aibridged.pb.go b/enterprise/aibridged/proto/aibridged.pb.go similarity index 60% rename from enterprise/x/aibridged/proto/aibridged.pb.go rename to enterprise/aibridged/proto/aibridged.pb.go index 41d31563b4043..a13a39ed95245 100644 --- a/enterprise/x/aibridged/proto/aibridged.pb.go +++ b/enterprise/aibridged/proto/aibridged.pb.go @@ -2,7 +2,7 @@ // versions: // protoc-gen-go v1.30.0 // protoc v4.23.4 -// source: enterprise/x/aibridged/proto/aibridged.proto +// source: enterprise/aibridged/proto/aibridged.proto package proto @@ -38,7 +38,7 @@ type RecordInterceptionRequest struct { func (x *RecordInterceptionRequest) Reset() { *x = RecordInterceptionRequest{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[0] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -51,7 +51,7 @@ func (x *RecordInterceptionRequest) String() string { func (*RecordInterceptionRequest) ProtoMessage() {} func (x *RecordInterceptionRequest) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[0] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -64,7 +64,7 @@ func (x *RecordInterceptionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordInterceptionRequest.ProtoReflect.Descriptor instead. func (*RecordInterceptionRequest) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{0} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{0} } func (x *RecordInterceptionRequest) GetId() string { @@ -118,7 +118,7 @@ type RecordInterceptionResponse struct { func (x *RecordInterceptionResponse) Reset() { *x = RecordInterceptionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[1] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -131,7 +131,7 @@ func (x *RecordInterceptionResponse) String() string { func (*RecordInterceptionResponse) ProtoMessage() {} func (x *RecordInterceptionResponse) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[1] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -144,7 +144,7 @@ func (x *RecordInterceptionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordInterceptionResponse.ProtoReflect.Descriptor instead. func (*RecordInterceptionResponse) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{1} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{1} } type RecordInterceptionEndedRequest struct { @@ -159,7 +159,7 @@ type RecordInterceptionEndedRequest struct { func (x *RecordInterceptionEndedRequest) Reset() { *x = RecordInterceptionEndedRequest{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[2] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -172,7 +172,7 @@ func (x *RecordInterceptionEndedRequest) String() string { func (*RecordInterceptionEndedRequest) ProtoMessage() {} func (x *RecordInterceptionEndedRequest) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[2] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -185,7 +185,7 @@ func (x *RecordInterceptionEndedRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordInterceptionEndedRequest.ProtoReflect.Descriptor instead. func (*RecordInterceptionEndedRequest) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{2} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{2} } func (x *RecordInterceptionEndedRequest) GetId() string { @@ -211,7 +211,7 @@ type RecordInterceptionEndedResponse struct { func (x *RecordInterceptionEndedResponse) Reset() { *x = RecordInterceptionEndedResponse{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[3] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -224,7 +224,7 @@ func (x *RecordInterceptionEndedResponse) String() string { func (*RecordInterceptionEndedResponse) ProtoMessage() {} func (x *RecordInterceptionEndedResponse) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[3] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -237,7 +237,7 @@ func (x *RecordInterceptionEndedResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordInterceptionEndedResponse.ProtoReflect.Descriptor instead. func (*RecordInterceptionEndedResponse) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{3} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{3} } type RecordTokenUsageRequest struct { @@ -256,7 +256,7 @@ type RecordTokenUsageRequest struct { func (x *RecordTokenUsageRequest) Reset() { *x = RecordTokenUsageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[4] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -269,7 +269,7 @@ func (x *RecordTokenUsageRequest) String() string { func (*RecordTokenUsageRequest) ProtoMessage() {} func (x *RecordTokenUsageRequest) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[4] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -282,7 +282,7 @@ func (x *RecordTokenUsageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordTokenUsageRequest.ProtoReflect.Descriptor instead. func (*RecordTokenUsageRequest) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{4} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{4} } func (x *RecordTokenUsageRequest) GetInterceptionId() string { @@ -336,7 +336,7 @@ type RecordTokenUsageResponse struct { func (x *RecordTokenUsageResponse) Reset() { *x = RecordTokenUsageResponse{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[5] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -349,7 +349,7 @@ func (x *RecordTokenUsageResponse) String() string { func (*RecordTokenUsageResponse) ProtoMessage() {} func (x *RecordTokenUsageResponse) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[5] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -362,7 +362,7 @@ func (x *RecordTokenUsageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordTokenUsageResponse.ProtoReflect.Descriptor instead. func (*RecordTokenUsageResponse) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{5} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{5} } type RecordPromptUsageRequest struct { @@ -380,7 +380,7 @@ type RecordPromptUsageRequest struct { func (x *RecordPromptUsageRequest) Reset() { *x = RecordPromptUsageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[6] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -393,7 +393,7 @@ func (x *RecordPromptUsageRequest) String() string { func (*RecordPromptUsageRequest) ProtoMessage() {} func (x *RecordPromptUsageRequest) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[6] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -406,7 +406,7 @@ func (x *RecordPromptUsageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordPromptUsageRequest.ProtoReflect.Descriptor instead. func (*RecordPromptUsageRequest) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{6} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{6} } func (x *RecordPromptUsageRequest) GetInterceptionId() string { @@ -453,7 +453,7 @@ type RecordPromptUsageResponse struct { func (x *RecordPromptUsageResponse) Reset() { *x = RecordPromptUsageResponse{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[7] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -466,7 +466,7 @@ func (x *RecordPromptUsageResponse) String() string { func (*RecordPromptUsageResponse) ProtoMessage() {} func (x *RecordPromptUsageResponse) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[7] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -479,7 +479,7 @@ func (x *RecordPromptUsageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordPromptUsageResponse.ProtoReflect.Descriptor instead. func (*RecordPromptUsageResponse) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{7} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{7} } type RecordToolUsageRequest struct { @@ -501,7 +501,7 @@ type RecordToolUsageRequest struct { func (x *RecordToolUsageRequest) Reset() { *x = RecordToolUsageRequest{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[8] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -514,7 +514,7 @@ func (x *RecordToolUsageRequest) String() string { func (*RecordToolUsageRequest) ProtoMessage() {} func (x *RecordToolUsageRequest) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[8] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -527,7 +527,7 @@ func (x *RecordToolUsageRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordToolUsageRequest.ProtoReflect.Descriptor instead. func (*RecordToolUsageRequest) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{8} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{8} } func (x *RecordToolUsageRequest) GetInterceptionId() string { @@ -602,7 +602,7 @@ type RecordToolUsageResponse struct { func (x *RecordToolUsageResponse) Reset() { *x = RecordToolUsageResponse{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[9] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -615,7 +615,7 @@ func (x *RecordToolUsageResponse) String() string { func (*RecordToolUsageResponse) ProtoMessage() {} func (x *RecordToolUsageResponse) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[9] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -628,7 +628,7 @@ func (x *RecordToolUsageResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RecordToolUsageResponse.ProtoReflect.Descriptor instead. func (*RecordToolUsageResponse) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{9} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{9} } type GetMCPServerConfigsRequest struct { @@ -642,7 +642,7 @@ type GetMCPServerConfigsRequest struct { func (x *GetMCPServerConfigsRequest) Reset() { *x = GetMCPServerConfigsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[10] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -655,7 +655,7 @@ func (x *GetMCPServerConfigsRequest) String() string { func (*GetMCPServerConfigsRequest) ProtoMessage() {} func (x *GetMCPServerConfigsRequest) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[10] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -668,7 +668,7 @@ func (x *GetMCPServerConfigsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMCPServerConfigsRequest.ProtoReflect.Descriptor instead. func (*GetMCPServerConfigsRequest) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{10} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{10} } func (x *GetMCPServerConfigsRequest) GetUserId() string { @@ -690,7 +690,7 @@ type GetMCPServerConfigsResponse struct { func (x *GetMCPServerConfigsResponse) Reset() { *x = GetMCPServerConfigsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[11] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -703,7 +703,7 @@ func (x *GetMCPServerConfigsResponse) String() string { func (*GetMCPServerConfigsResponse) ProtoMessage() {} func (x *GetMCPServerConfigsResponse) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[11] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -716,7 +716,7 @@ func (x *GetMCPServerConfigsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMCPServerConfigsResponse.ProtoReflect.Descriptor instead. func (*GetMCPServerConfigsResponse) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{11} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{11} } func (x *GetMCPServerConfigsResponse) GetCoderMcpConfig() *MCPServerConfig { @@ -747,7 +747,7 @@ type MCPServerConfig struct { func (x *MCPServerConfig) Reset() { *x = MCPServerConfig{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[12] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -760,7 +760,7 @@ func (x *MCPServerConfig) String() string { func (*MCPServerConfig) ProtoMessage() {} func (x *MCPServerConfig) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[12] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -773,7 +773,7 @@ func (x *MCPServerConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use MCPServerConfig.ProtoReflect.Descriptor instead. func (*MCPServerConfig) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{12} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{12} } func (x *MCPServerConfig) GetId() string { @@ -816,7 +816,7 @@ type GetMCPServerAccessTokensBatchRequest struct { func (x *GetMCPServerAccessTokensBatchRequest) Reset() { *x = GetMCPServerAccessTokensBatchRequest{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[13] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -829,7 +829,7 @@ func (x *GetMCPServerAccessTokensBatchRequest) String() string { func (*GetMCPServerAccessTokensBatchRequest) ProtoMessage() {} func (x *GetMCPServerAccessTokensBatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[13] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -842,7 +842,7 @@ func (x *GetMCPServerAccessTokensBatchRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use GetMCPServerAccessTokensBatchRequest.ProtoReflect.Descriptor instead. func (*GetMCPServerAccessTokensBatchRequest) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{13} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{13} } func (x *GetMCPServerAccessTokensBatchRequest) GetUserId() string { @@ -873,7 +873,7 @@ type GetMCPServerAccessTokensBatchResponse struct { func (x *GetMCPServerAccessTokensBatchResponse) Reset() { *x = GetMCPServerAccessTokensBatchResponse{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[14] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -886,7 +886,7 @@ func (x *GetMCPServerAccessTokensBatchResponse) String() string { func (*GetMCPServerAccessTokensBatchResponse) ProtoMessage() {} func (x *GetMCPServerAccessTokensBatchResponse) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[14] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -899,7 +899,7 @@ func (x *GetMCPServerAccessTokensBatchResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use GetMCPServerAccessTokensBatchResponse.ProtoReflect.Descriptor instead. func (*GetMCPServerAccessTokensBatchResponse) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{14} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{14} } func (x *GetMCPServerAccessTokensBatchResponse) GetAccessTokens() map[string]string { @@ -927,7 +927,7 @@ type IsAuthorizedRequest struct { func (x *IsAuthorizedRequest) Reset() { *x = IsAuthorizedRequest{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[15] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -940,7 +940,7 @@ func (x *IsAuthorizedRequest) String() string { func (*IsAuthorizedRequest) ProtoMessage() {} func (x *IsAuthorizedRequest) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[15] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -953,7 +953,7 @@ func (x *IsAuthorizedRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IsAuthorizedRequest.ProtoReflect.Descriptor instead. func (*IsAuthorizedRequest) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{15} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{15} } func (x *IsAuthorizedRequest) GetKey() string { @@ -974,7 +974,7 @@ type IsAuthorizedResponse struct { func (x *IsAuthorizedResponse) Reset() { *x = IsAuthorizedResponse{} if protoimpl.UnsafeEnabled { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[16] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -987,7 +987,7 @@ func (x *IsAuthorizedResponse) String() string { func (*IsAuthorizedResponse) ProtoMessage() {} func (x *IsAuthorizedResponse) ProtoReflect() protoreflect.Message { - mi := &file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[16] + mi := &file_enterprise_aibridged_proto_aibridged_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1000,7 +1000,7 @@ func (x *IsAuthorizedResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IsAuthorizedResponse.ProtoReflect.Descriptor instead. func (*IsAuthorizedResponse) Descriptor() ([]byte, []int) { - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{16} + return file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP(), []int{16} } func (x *IsAuthorizedResponse) GetOwnerId() string { @@ -1010,257 +1010,256 @@ func (x *IsAuthorizedResponse) GetOwnerId() string { return "" } -var File_enterprise_x_aibridged_proto_aibridged_proto protoreflect.FileDescriptor - -var file_enterprise_x_aibridged_proto_aibridged_proto_rawDesc = []byte{ - 0x0a, 0x2c, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x2f, 0x78, 0x2f, 0x61, - 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, - 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0xda, 0x02, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, - 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x14, - 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, - 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x4a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x51, 0x0a, 0x0d, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1c, - 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x67, 0x0a, 0x1e, +var File_enterprise_aibridged_proto_aibridged_proto protoreflect.FileDescriptor + +var file_enterprise_aibridged_proto_aibridged_proto_rawDesc = []byte{ + 0x0a, 0x2a, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x2f, 0x61, 0x69, 0x62, + 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x69, 0x62, + 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0xda, 0x02, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, + 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x6f, 0x64, + 0x65, 0x6c, 0x12, 0x4a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, + 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x51, 0x0a, 0x0d, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, + 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1c, 0x0a, 0x1a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x35, - 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, - 0x64, 0x65, 0x64, 0x41, 0x74, 0x22, 0x21, 0x0a, 0x1f, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf9, 0x02, 0x0a, 0x17, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x67, 0x0a, 0x1e, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x35, 0x0a, 0x08, + 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x65, + 0x64, 0x41, 0x74, 0x22, 0x21, 0x0a, 0x1f, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xf9, 0x02, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, + 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x73, 0x67, + 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x48, 0x0a, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x1a, + 0x51, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x1a, 0x0a, 0x18, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcb, + 0x02, 0x0a, 0x18, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, + 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x6f, + 0x6d, 0x70, 0x74, 0x12, 0x49, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, + 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x51, 0x0a, 0x0d, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, + 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1b, 0x0a, 0x19, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xed, 0x03, 0x0a, 0x16, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, - 0x73, 0x67, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x69, 0x6e, 0x70, 0x75, - 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x48, 0x0a, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x1a, 0x51, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1a, 0x0a, 0x18, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0xcb, 0x02, 0x0a, 0x18, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, - 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, - 0x0f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, - 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x12, 0x49, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x51, 0x0a, 0x0d, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1b, - 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xed, 0x03, 0x0a, 0x16, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, - 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x6f, - 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x6f, 0x6f, 0x6c, 0x12, 0x14, - 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, - 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x12, 0x2e, 0x0a, 0x10, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0f, 0x69, 0x6e, - 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, - 0x12, 0x47, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x41, 0x74, 0x1a, 0x51, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x19, 0x0a, 0x17, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xb2, 0x01, - 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, - 0x10, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x6d, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x0e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x4d, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x51, 0x0a, 0x19, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, - 0x5f, 0x6d, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x43, 0x50, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x16, 0x65, 0x78, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x0f, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x6f, 0x6c, - 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x74, 0x6f, 0x6f, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x67, - 0x65, 0x78, 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x6f, 0x6c, 0x5f, 0x64, 0x65, 0x6e, 0x79, 0x5f, - 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x6f, 0x6f, - 0x6c, 0x44, 0x65, 0x6e, 0x79, 0x52, 0x65, 0x67, 0x65, 0x78, 0x22, 0x72, 0x0a, 0x24, 0x47, 0x65, - 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x67, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x55, 0x72, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x6f, 0x6f, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x6f, 0x6f, 0x6c, 0x12, 0x14, 0x0a, 0x05, + 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6e, 0x70, + 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x2e, + 0x0a, 0x10, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0f, 0x69, 0x6e, 0x76, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x12, 0x47, + 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, + 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x1a, 0x51, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x75, 0x72, 0x6c, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x19, 0x0a, 0x17, 0x52, 0x65, 0x63, + 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x6d, - 0x63, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x6d, 0x63, 0x70, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x73, 0x22, 0xda, - 0x02, 0x0a, 0x25, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x3e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x50, 0x0a, - 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xb2, 0x01, 0x0a, 0x1b, + 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x10, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x6d, 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x43, + 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x4d, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x51, 0x0a, + 0x19, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, + 0x63, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x16, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x63, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, + 0x22, 0x85, 0x01, 0x0a, 0x0f, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x6f, 0x6c, 0x5f, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x74, 0x6f, 0x6f, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x67, 0x65, 0x78, + 0x12, 0x26, 0x0a, 0x0f, 0x74, 0x6f, 0x6f, 0x6c, 0x5f, 0x64, 0x65, 0x6e, 0x79, 0x5f, 0x72, 0x65, + 0x67, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x6f, 0x6f, 0x6c, 0x44, + 0x65, 0x6e, 0x79, 0x52, 0x65, 0x67, 0x65, 0x78, 0x22, 0x72, 0x0a, 0x24, 0x47, 0x65, 0x74, 0x4d, + 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x15, 0x6d, 0x63, 0x70, + 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, + 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x6d, 0x63, 0x70, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x73, 0x22, 0xda, 0x02, 0x0a, + 0x25, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, - 0x3f, 0x0a, 0x11, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x1a, 0x39, 0x0a, 0x0b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x27, 0x0a, 0x13, 0x49, - 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x22, 0x31, 0x0a, 0x14, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x32, 0xce, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x12, 0x59, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x68, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x12, 0x25, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, - 0x0a, 0x11, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xeb, 0x01, 0x0a, 0x0f, 0x4d, 0x43, 0x50, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x5c, 0x0a, 0x13, - 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x73, 0x12, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, - 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, - 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x1d, 0x47, 0x65, - 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x2b, 0x2e, 0x70, 0x72, + 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x61, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x50, 0x0a, 0x06, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, - 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x55, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x1a, 0x3f, 0x0a, + 0x11, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x39, + 0x0a, 0x0b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x27, 0x0a, 0x13, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2b, 0x5a, - 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x69, 0x62, 0x72, 0x69, - 0x64, 0x67, 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x22, 0x31, 0x0a, 0x14, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x49, 0x64, 0x32, 0xce, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x12, 0x59, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, + 0x17, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x12, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, + 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xeb, 0x01, 0x0a, 0x0f, 0x4d, 0x43, 0x50, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x5c, 0x0a, 0x13, 0x47, 0x65, + 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x73, 0x12, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, + 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4d, + 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, + 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x55, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2b, 0x5a, 0x29, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, + 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( - file_enterprise_x_aibridged_proto_aibridged_proto_rawDescOnce sync.Once - file_enterprise_x_aibridged_proto_aibridged_proto_rawDescData = file_enterprise_x_aibridged_proto_aibridged_proto_rawDesc + file_enterprise_aibridged_proto_aibridged_proto_rawDescOnce sync.Once + file_enterprise_aibridged_proto_aibridged_proto_rawDescData = file_enterprise_aibridged_proto_aibridged_proto_rawDesc ) -func file_enterprise_x_aibridged_proto_aibridged_proto_rawDescGZIP() []byte { - file_enterprise_x_aibridged_proto_aibridged_proto_rawDescOnce.Do(func() { - file_enterprise_x_aibridged_proto_aibridged_proto_rawDescData = protoimpl.X.CompressGZIP(file_enterprise_x_aibridged_proto_aibridged_proto_rawDescData) +func file_enterprise_aibridged_proto_aibridged_proto_rawDescGZIP() []byte { + file_enterprise_aibridged_proto_aibridged_proto_rawDescOnce.Do(func() { + file_enterprise_aibridged_proto_aibridged_proto_rawDescData = protoimpl.X.CompressGZIP(file_enterprise_aibridged_proto_aibridged_proto_rawDescData) }) - return file_enterprise_x_aibridged_proto_aibridged_proto_rawDescData + return file_enterprise_aibridged_proto_aibridged_proto_rawDescData } -var file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes = make([]protoimpl.MessageInfo, 23) -var file_enterprise_x_aibridged_proto_aibridged_proto_goTypes = []interface{}{ +var file_enterprise_aibridged_proto_aibridged_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_enterprise_aibridged_proto_aibridged_proto_goTypes = []interface{}{ (*RecordInterceptionRequest)(nil), // 0: proto.RecordInterceptionRequest (*RecordInterceptionResponse)(nil), // 1: proto.RecordInterceptionResponse (*RecordInterceptionEndedRequest)(nil), // 2: proto.RecordInterceptionEndedRequest @@ -1287,7 +1286,7 @@ var file_enterprise_x_aibridged_proto_aibridged_proto_goTypes = []interface{}{ (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp (*anypb.Any)(nil), // 24: google.protobuf.Any } -var file_enterprise_x_aibridged_proto_aibridged_proto_depIdxs = []int32{ +var file_enterprise_aibridged_proto_aibridged_proto_depIdxs = []int32{ 17, // 0: proto.RecordInterceptionRequest.metadata:type_name -> proto.RecordInterceptionRequest.MetadataEntry 23, // 1: proto.RecordInterceptionRequest.started_at:type_name -> google.protobuf.Timestamp 23, // 2: proto.RecordInterceptionEndedRequest.ended_at:type_name -> google.protobuf.Timestamp @@ -1328,13 +1327,13 @@ var file_enterprise_x_aibridged_proto_aibridged_proto_depIdxs = []int32{ 0, // [0:17] is the sub-list for field type_name } -func init() { file_enterprise_x_aibridged_proto_aibridged_proto_init() } -func file_enterprise_x_aibridged_proto_aibridged_proto_init() { - if File_enterprise_x_aibridged_proto_aibridged_proto != nil { +func init() { file_enterprise_aibridged_proto_aibridged_proto_init() } +func file_enterprise_aibridged_proto_aibridged_proto_init() { + if File_enterprise_aibridged_proto_aibridged_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordInterceptionRequest); i { case 0: return &v.state @@ -1346,7 +1345,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordInterceptionResponse); i { case 0: return &v.state @@ -1358,7 +1357,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordInterceptionEndedRequest); i { case 0: return &v.state @@ -1370,7 +1369,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordInterceptionEndedResponse); i { case 0: return &v.state @@ -1382,7 +1381,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordTokenUsageRequest); i { case 0: return &v.state @@ -1394,7 +1393,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordTokenUsageResponse); i { case 0: return &v.state @@ -1406,7 +1405,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordPromptUsageRequest); i { case 0: return &v.state @@ -1418,7 +1417,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordPromptUsageResponse); i { case 0: return &v.state @@ -1430,7 +1429,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordToolUsageRequest); i { case 0: return &v.state @@ -1442,7 +1441,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RecordToolUsageResponse); i { case 0: return &v.state @@ -1454,7 +1453,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetMCPServerConfigsRequest); i { case 0: return &v.state @@ -1466,7 +1465,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetMCPServerConfigsResponse); i { case 0: return &v.state @@ -1478,7 +1477,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MCPServerConfig); i { case 0: return &v.state @@ -1490,7 +1489,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetMCPServerAccessTokensBatchRequest); i { case 0: return &v.state @@ -1502,7 +1501,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetMCPServerAccessTokensBatchResponse); i { case 0: return &v.state @@ -1514,7 +1513,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*IsAuthorizedRequest); i { case 0: return &v.state @@ -1526,7 +1525,7 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { return nil } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*IsAuthorizedResponse); i { case 0: return &v.state @@ -1539,23 +1538,23 @@ func file_enterprise_x_aibridged_proto_aibridged_proto_init() { } } } - file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes[8].OneofWrappers = []interface{}{} + file_enterprise_aibridged_proto_aibridged_proto_msgTypes[8].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_enterprise_x_aibridged_proto_aibridged_proto_rawDesc, + RawDescriptor: file_enterprise_aibridged_proto_aibridged_proto_rawDesc, NumEnums: 0, NumMessages: 23, NumExtensions: 0, NumServices: 3, }, - GoTypes: file_enterprise_x_aibridged_proto_aibridged_proto_goTypes, - DependencyIndexes: file_enterprise_x_aibridged_proto_aibridged_proto_depIdxs, - MessageInfos: file_enterprise_x_aibridged_proto_aibridged_proto_msgTypes, + GoTypes: file_enterprise_aibridged_proto_aibridged_proto_goTypes, + DependencyIndexes: file_enterprise_aibridged_proto_aibridged_proto_depIdxs, + MessageInfos: file_enterprise_aibridged_proto_aibridged_proto_msgTypes, }.Build() - File_enterprise_x_aibridged_proto_aibridged_proto = out.File - file_enterprise_x_aibridged_proto_aibridged_proto_rawDesc = nil - file_enterprise_x_aibridged_proto_aibridged_proto_goTypes = nil - file_enterprise_x_aibridged_proto_aibridged_proto_depIdxs = nil + File_enterprise_aibridged_proto_aibridged_proto = out.File + file_enterprise_aibridged_proto_aibridged_proto_rawDesc = nil + file_enterprise_aibridged_proto_aibridged_proto_goTypes = nil + file_enterprise_aibridged_proto_aibridged_proto_depIdxs = nil } diff --git a/enterprise/x/aibridged/proto/aibridged.proto b/enterprise/aibridged/proto/aibridged.proto similarity index 100% rename from enterprise/x/aibridged/proto/aibridged.proto rename to enterprise/aibridged/proto/aibridged.proto diff --git a/enterprise/x/aibridged/proto/aibridged_drpc.pb.go b/enterprise/aibridged/proto/aibridged_drpc.pb.go similarity index 84% rename from enterprise/x/aibridged/proto/aibridged_drpc.pb.go rename to enterprise/aibridged/proto/aibridged_drpc.pb.go index 4c7cb3c190764..1309957d153d5 100644 --- a/enterprise/x/aibridged/proto/aibridged_drpc.pb.go +++ b/enterprise/aibridged/proto/aibridged_drpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-drpc. DO NOT EDIT. // protoc-gen-go-drpc version: v0.0.34 -// source: enterprise/x/aibridged/proto/aibridged.proto +// source: enterprise/aibridged/proto/aibridged.proto package proto @@ -13,25 +13,25 @@ import ( drpcerr "storj.io/drpc/drpcerr" ) -type drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto struct{} +type drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto struct{} -func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) Marshal(msg drpc.Message) ([]byte, error) { +func (drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto) Marshal(msg drpc.Message) ([]byte, error) { return proto.Marshal(msg.(proto.Message)) } -func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) MarshalAppend(buf []byte, msg drpc.Message) ([]byte, error) { +func (drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto) MarshalAppend(buf []byte, msg drpc.Message) ([]byte, error) { return proto.MarshalOptions{}.MarshalAppend(buf, msg.(proto.Message)) } -func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) Unmarshal(buf []byte, msg drpc.Message) error { +func (drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto) Unmarshal(buf []byte, msg drpc.Message) error { return proto.Unmarshal(buf, msg.(proto.Message)) } -func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) JSONMarshal(msg drpc.Message) ([]byte, error) { +func (drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto) JSONMarshal(msg drpc.Message) ([]byte, error) { return protojson.Marshal(msg.(proto.Message)) } -func (drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto) JSONUnmarshal(buf []byte, msg drpc.Message) error { +func (drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto) JSONUnmarshal(buf []byte, msg drpc.Message) error { return protojson.Unmarshal(buf, msg.(proto.Message)) } @@ -57,7 +57,7 @@ func (c *drpcRecorderClient) DRPCConn() drpc.Conn { return c.cc } func (c *drpcRecorderClient) RecordInterception(ctx context.Context, in *RecordInterceptionRequest) (*RecordInterceptionResponse, error) { out := new(RecordInterceptionResponse) - err := c.cc.Invoke(ctx, "/proto.Recorder/RecordInterception", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Recorder/RecordInterception", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -66,7 +66,7 @@ func (c *drpcRecorderClient) RecordInterception(ctx context.Context, in *RecordI func (c *drpcRecorderClient) RecordInterceptionEnded(ctx context.Context, in *RecordInterceptionEndedRequest) (*RecordInterceptionEndedResponse, error) { out := new(RecordInterceptionEndedResponse) - err := c.cc.Invoke(ctx, "/proto.Recorder/RecordInterceptionEnded", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Recorder/RecordInterceptionEnded", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -75,7 +75,7 @@ func (c *drpcRecorderClient) RecordInterceptionEnded(ctx context.Context, in *Re func (c *drpcRecorderClient) RecordTokenUsage(ctx context.Context, in *RecordTokenUsageRequest) (*RecordTokenUsageResponse, error) { out := new(RecordTokenUsageResponse) - err := c.cc.Invoke(ctx, "/proto.Recorder/RecordTokenUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Recorder/RecordTokenUsage", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -84,7 +84,7 @@ func (c *drpcRecorderClient) RecordTokenUsage(ctx context.Context, in *RecordTok func (c *drpcRecorderClient) RecordPromptUsage(ctx context.Context, in *RecordPromptUsageRequest) (*RecordPromptUsageResponse, error) { out := new(RecordPromptUsageResponse) - err := c.cc.Invoke(ctx, "/proto.Recorder/RecordPromptUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Recorder/RecordPromptUsage", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -93,7 +93,7 @@ func (c *drpcRecorderClient) RecordPromptUsage(ctx context.Context, in *RecordPr func (c *drpcRecorderClient) RecordToolUsage(ctx context.Context, in *RecordToolUsageRequest) (*RecordToolUsageResponse, error) { out := new(RecordToolUsageResponse) - err := c.cc.Invoke(ctx, "/proto.Recorder/RecordToolUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Recorder/RecordToolUsage", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -137,7 +137,7 @@ func (DRPCRecorderDescription) NumMethods() int { return 5 } func (DRPCRecorderDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) { switch n { case 0: - return "/proto.Recorder/RecordInterception", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, + return "/proto.Recorder/RecordInterception", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCRecorderServer). RecordInterception( @@ -146,7 +146,7 @@ func (DRPCRecorderDescription) Method(n int) (string, drpc.Encoding, drpc.Receiv ) }, DRPCRecorderServer.RecordInterception, true case 1: - return "/proto.Recorder/RecordInterceptionEnded", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, + return "/proto.Recorder/RecordInterceptionEnded", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCRecorderServer). RecordInterceptionEnded( @@ -155,7 +155,7 @@ func (DRPCRecorderDescription) Method(n int) (string, drpc.Encoding, drpc.Receiv ) }, DRPCRecorderServer.RecordInterceptionEnded, true case 2: - return "/proto.Recorder/RecordTokenUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, + return "/proto.Recorder/RecordTokenUsage", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCRecorderServer). RecordTokenUsage( @@ -164,7 +164,7 @@ func (DRPCRecorderDescription) Method(n int) (string, drpc.Encoding, drpc.Receiv ) }, DRPCRecorderServer.RecordTokenUsage, true case 3: - return "/proto.Recorder/RecordPromptUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, + return "/proto.Recorder/RecordPromptUsage", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCRecorderServer). RecordPromptUsage( @@ -173,7 +173,7 @@ func (DRPCRecorderDescription) Method(n int) (string, drpc.Encoding, drpc.Receiv ) }, DRPCRecorderServer.RecordPromptUsage, true case 4: - return "/proto.Recorder/RecordToolUsage", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, + return "/proto.Recorder/RecordToolUsage", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCRecorderServer). RecordToolUsage( @@ -200,7 +200,7 @@ type drpcRecorder_RecordInterceptionStream struct { } func (x *drpcRecorder_RecordInterceptionStream) SendAndClose(m *RecordInterceptionResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -216,7 +216,7 @@ type drpcRecorder_RecordInterceptionEndedStream struct { } func (x *drpcRecorder_RecordInterceptionEndedStream) SendAndClose(m *RecordInterceptionEndedResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -232,7 +232,7 @@ type drpcRecorder_RecordTokenUsageStream struct { } func (x *drpcRecorder_RecordTokenUsageStream) SendAndClose(m *RecordTokenUsageResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -248,7 +248,7 @@ type drpcRecorder_RecordPromptUsageStream struct { } func (x *drpcRecorder_RecordPromptUsageStream) SendAndClose(m *RecordPromptUsageResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -264,7 +264,7 @@ type drpcRecorder_RecordToolUsageStream struct { } func (x *drpcRecorder_RecordToolUsageStream) SendAndClose(m *RecordToolUsageResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -289,7 +289,7 @@ func (c *drpcMCPConfiguratorClient) DRPCConn() drpc.Conn { return c.cc } func (c *drpcMCPConfiguratorClient) GetMCPServerConfigs(ctx context.Context, in *GetMCPServerConfigsRequest) (*GetMCPServerConfigsResponse, error) { out := new(GetMCPServerConfigsResponse) - err := c.cc.Invoke(ctx, "/proto.MCPConfigurator/GetMCPServerConfigs", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.MCPConfigurator/GetMCPServerConfigs", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -298,7 +298,7 @@ func (c *drpcMCPConfiguratorClient) GetMCPServerConfigs(ctx context.Context, in func (c *drpcMCPConfiguratorClient) GetMCPServerAccessTokensBatch(ctx context.Context, in *GetMCPServerAccessTokensBatchRequest) (*GetMCPServerAccessTokensBatchResponse, error) { out := new(GetMCPServerAccessTokensBatchResponse) - err := c.cc.Invoke(ctx, "/proto.MCPConfigurator/GetMCPServerAccessTokensBatch", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.MCPConfigurator/GetMCPServerAccessTokensBatch", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -327,7 +327,7 @@ func (DRPCMCPConfiguratorDescription) NumMethods() int { return 2 } func (DRPCMCPConfiguratorDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) { switch n { case 0: - return "/proto.MCPConfigurator/GetMCPServerConfigs", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, + return "/proto.MCPConfigurator/GetMCPServerConfigs", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCMCPConfiguratorServer). GetMCPServerConfigs( @@ -336,7 +336,7 @@ func (DRPCMCPConfiguratorDescription) Method(n int) (string, drpc.Encoding, drpc ) }, DRPCMCPConfiguratorServer.GetMCPServerConfigs, true case 1: - return "/proto.MCPConfigurator/GetMCPServerAccessTokensBatch", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, + return "/proto.MCPConfigurator/GetMCPServerAccessTokensBatch", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCMCPConfiguratorServer). GetMCPServerAccessTokensBatch( @@ -363,7 +363,7 @@ type drpcMCPConfigurator_GetMCPServerConfigsStream struct { } func (x *drpcMCPConfigurator_GetMCPServerConfigsStream) SendAndClose(m *GetMCPServerConfigsResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -379,7 +379,7 @@ type drpcMCPConfigurator_GetMCPServerAccessTokensBatchStream struct { } func (x *drpcMCPConfigurator_GetMCPServerAccessTokensBatchStream) SendAndClose(m *GetMCPServerAccessTokensBatchResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() @@ -403,7 +403,7 @@ func (c *drpcAuthorizerClient) DRPCConn() drpc.Conn { return c.cc } func (c *drpcAuthorizerClient) IsAuthorized(ctx context.Context, in *IsAuthorizedRequest) (*IsAuthorizedResponse, error) { out := new(IsAuthorizedResponse) - err := c.cc.Invoke(ctx, "/proto.Authorizer/IsAuthorized", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, in, out) + err := c.cc.Invoke(ctx, "/proto.Authorizer/IsAuthorized", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, in, out) if err != nil { return nil, err } @@ -427,7 +427,7 @@ func (DRPCAuthorizerDescription) NumMethods() int { return 1 } func (DRPCAuthorizerDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) { switch n { case 0: - return "/proto.Authorizer/IsAuthorized", drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}, + return "/proto.Authorizer/IsAuthorized", drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { return srv.(DRPCAuthorizerServer). IsAuthorized( @@ -454,7 +454,7 @@ type drpcAuthorizer_IsAuthorizedStream struct { } func (x *drpcAuthorizer_IsAuthorizedStream) SendAndClose(m *IsAuthorizedResponse) error { - if err := x.MsgSend(m, drpcEncoding_File_enterprise_x_aibridged_proto_aibridged_proto{}); err != nil { + if err := x.MsgSend(m, drpcEncoding_File_enterprise_aibridged_proto_aibridged_proto{}); err != nil { return err } return x.CloseSend() diff --git a/enterprise/x/aibridged/request.go b/enterprise/aibridged/request.go similarity index 100% rename from enterprise/x/aibridged/request.go rename to enterprise/aibridged/request.go diff --git a/enterprise/x/aibridged/server.go b/enterprise/aibridged/server.go similarity index 68% rename from enterprise/x/aibridged/server.go rename to enterprise/aibridged/server.go index 713ea2a0cd126..052c94dad4a9e 100644 --- a/enterprise/x/aibridged/server.go +++ b/enterprise/aibridged/server.go @@ -1,6 +1,6 @@ package aibridged -import "github.com/coder/coder/v2/enterprise/x/aibridged/proto" +import "github.com/coder/coder/v2/enterprise/aibridged/proto" type DRPCServer interface { proto.DRPCRecorderServer diff --git a/enterprise/x/aibridged/translator.go b/enterprise/aibridged/translator.go similarity index 98% rename from enterprise/x/aibridged/translator.go rename to enterprise/aibridged/translator.go index bfc39d834ad2c..f36185715a745 100644 --- a/enterprise/x/aibridged/translator.go +++ b/enterprise/aibridged/translator.go @@ -11,7 +11,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/coder/coder/v2/coderd/util/ptr" - "github.com/coder/coder/v2/enterprise/x/aibridged/proto" + "github.com/coder/coder/v2/enterprise/aibridged/proto" "github.com/coder/aibridge" ) diff --git a/enterprise/x/aibridged/utils_test.go b/enterprise/aibridged/utils_test.go similarity index 100% rename from enterprise/x/aibridged/utils_test.go rename to enterprise/aibridged/utils_test.go diff --git a/enterprise/x/aibridgedserver/aibridgedserver.go b/enterprise/aibridgedserver/aibridgedserver.go similarity index 99% rename from enterprise/x/aibridgedserver/aibridgedserver.go rename to enterprise/aibridgedserver/aibridgedserver.go index 2c5e3ff71c072..6adf7b793c1d2 100644 --- a/enterprise/x/aibridgedserver/aibridgedserver.go +++ b/enterprise/aibridgedserver/aibridgedserver.go @@ -24,8 +24,8 @@ import ( "github.com/coder/coder/v2/coderd/httpmw" codermcp "github.com/coder/coder/v2/coderd/mcp" "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/enterprise/x/aibridged" - "github.com/coder/coder/v2/enterprise/x/aibridged/proto" + "github.com/coder/coder/v2/enterprise/aibridged" + "github.com/coder/coder/v2/enterprise/aibridged/proto" ) var ( diff --git a/enterprise/x/aibridgedserver/aibridgedserver_internal_test.go b/enterprise/aibridgedserver/aibridgedserver_internal_test.go similarity index 100% rename from enterprise/x/aibridgedserver/aibridgedserver_internal_test.go rename to enterprise/aibridgedserver/aibridgedserver_internal_test.go diff --git a/enterprise/x/aibridgedserver/aibridgedserver_test.go b/enterprise/aibridgedserver/aibridgedserver_test.go similarity index 99% rename from enterprise/x/aibridgedserver/aibridgedserver_test.go rename to enterprise/aibridgedserver/aibridgedserver_test.go index 4f9f892bc886a..27598c79857f1 100644 --- a/enterprise/x/aibridgedserver/aibridgedserver_test.go +++ b/enterprise/aibridgedserver/aibridgedserver_test.go @@ -28,9 +28,9 @@ import ( codermcp "github.com/coder/coder/v2/coderd/mcp" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/cryptorand" - "github.com/coder/coder/v2/enterprise/x/aibridged" - "github.com/coder/coder/v2/enterprise/x/aibridged/proto" - "github.com/coder/coder/v2/enterprise/x/aibridgedserver" + "github.com/coder/coder/v2/enterprise/aibridged" + "github.com/coder/coder/v2/enterprise/aibridged/proto" + "github.com/coder/coder/v2/enterprise/aibridgedserver" "github.com/coder/coder/v2/testutil" ) diff --git a/enterprise/cli/aibridged.go b/enterprise/cli/aibridged.go index 17bb5ebe681fa..b2dc3d7725b93 100644 --- a/enterprise/cli/aibridged.go +++ b/enterprise/cli/aibridged.go @@ -9,8 +9,8 @@ import ( "github.com/coder/aibridge" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/enterprise/aibridged" "github.com/coder/coder/v2/enterprise/coderd" - "github.com/coder/coder/v2/enterprise/x/aibridged" ) func newAIBridgeDaemon(coderAPI *coderd.API) (*aibridged.Server, error) { diff --git a/enterprise/cli/server.go b/enterprise/cli/server.go index 9dab05546f6c6..bc77bc54ba522 100644 --- a/enterprise/cli/server.go +++ b/enterprise/cli/server.go @@ -16,6 +16,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/cryptorand" + "github.com/coder/coder/v2/enterprise/aibridged" "github.com/coder/coder/v2/enterprise/audit" "github.com/coder/coder/v2/enterprise/audit/backends" "github.com/coder/coder/v2/enterprise/coderd" @@ -23,7 +24,6 @@ import ( "github.com/coder/coder/v2/enterprise/coderd/usage" "github.com/coder/coder/v2/enterprise/dbcrypt" "github.com/coder/coder/v2/enterprise/trialer" - "github.com/coder/coder/v2/enterprise/x/aibridged" "github.com/coder/coder/v2/tailnet" "github.com/coder/quartz" "github.com/coder/serpent" diff --git a/enterprise/coderd/aibridged.go b/enterprise/coderd/aibridged.go index bf991103b1f52..285575df33862 100644 --- a/enterprise/coderd/aibridged.go +++ b/enterprise/coderd/aibridged.go @@ -14,9 +14,9 @@ import ( "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/codersdk/drpcsdk" - "github.com/coder/coder/v2/enterprise/x/aibridged" - aibridgedproto "github.com/coder/coder/v2/enterprise/x/aibridged/proto" - "github.com/coder/coder/v2/enterprise/x/aibridgedserver" + "github.com/coder/coder/v2/enterprise/aibridged" + aibridgedproto "github.com/coder/coder/v2/enterprise/aibridged/proto" + "github.com/coder/coder/v2/enterprise/aibridgedserver" ) // RegisterInMemoryAIBridgedHTTPHandler mounts [aibridged.Server]'s HTTP router onto From b20fd6f2c17cec7be7485ec19a5c4e1aca499703 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 29 Oct 2025 07:18:54 -0600 Subject: [PATCH 007/255] chore: graduate aibridge API out of experimental (#20523) --- coderd/apidoc/docs.go | 2 +- coderd/apidoc/swagger.json | 2 +- codersdk/aibridge.go | 4 ++-- docs/reference/api/aibridge.md | 4 ++-- enterprise/aibridged/aibridged.go | 2 +- enterprise/coderd/aibridge.go | 2 +- enterprise/coderd/aibridge_test.go | 30 ++++++++++++------------------ enterprise/coderd/coderd.go | 4 ++-- 8 files changed, 22 insertions(+), 28 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 89af9e2386180..344a0299f8405 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -85,7 +85,7 @@ const docTemplate = `{ } } }, - "/api/experimental/aibridge/interceptions": { + "/aibridge/interceptions": { "get": { "security": [ { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 759647d9b1ba7..b472f2f4ef53f 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -65,7 +65,7 @@ } } }, - "/api/experimental/aibridge/interceptions": { + "/aibridge/interceptions": { "get": { "security": [ { diff --git a/codersdk/aibridge.go b/codersdk/aibridge.go index b627f5e9d5ef7..a322187bb23c3 100644 --- a/codersdk/aibridge.go +++ b/codersdk/aibridge.go @@ -113,8 +113,8 @@ func (f AIBridgeListInterceptionsFilter) asRequestOption() RequestOption { // AIBridgeListInterceptions returns AIBridge interceptions with the given // filter. -func (c *ExperimentalClient) AIBridgeListInterceptions(ctx context.Context, filter AIBridgeListInterceptionsFilter) (AIBridgeListInterceptionsResponse, error) { - res, err := c.Request(ctx, http.MethodGet, "/api/experimental/aibridge/interceptions", nil, filter.asRequestOption(), filter.Pagination.asRequestOption(), filter.Pagination.asRequestOption()) +func (c *Client) AIBridgeListInterceptions(ctx context.Context, filter AIBridgeListInterceptionsFilter) (AIBridgeListInterceptionsResponse, error) { + res, err := c.Request(ctx, http.MethodGet, "/api/v2/aibridge/interceptions", nil, filter.asRequestOption(), filter.Pagination.asRequestOption(), filter.Pagination.asRequestOption()) if err != nil { return AIBridgeListInterceptionsResponse{}, err } diff --git a/docs/reference/api/aibridge.md b/docs/reference/api/aibridge.md index d2be736eb32b2..7e3a23fc5ec21 100644 --- a/docs/reference/api/aibridge.md +++ b/docs/reference/api/aibridge.md @@ -6,12 +6,12 @@ ```shell # Example request using curl -curl -X GET http://coder-server:8080/api/v2/api/experimental/aibridge/interceptions \ +curl -X GET http://coder-server:8080/api/v2/aibridge/interceptions \ -H 'Accept: application/json' \ -H 'Coder-Session-Token: API_KEY' ``` -`GET /api/experimental/aibridge/interceptions` +`GET /aibridge/interceptions` ### Parameters diff --git a/enterprise/aibridged/aibridged.go b/enterprise/aibridged/aibridged.go index a1fa4022ff960..fcec1629b8701 100644 --- a/enterprise/aibridged/aibridged.go +++ b/enterprise/aibridged/aibridged.go @@ -19,7 +19,7 @@ var _ io.Closer = &Server{} // Server provides the AI Bridge functionality. // It is responsible for: -// - receiving requests on /api/experimental/aibridged/* // TODO: update endpoint once out of experimental +// - receiving requests on /api/v2/aibridged/* // - manipulating the requests // - relaying requests to upstream AI services and relaying responses to caller // diff --git a/enterprise/coderd/aibridge.go b/enterprise/coderd/aibridge.go index dab93d8992a79..bdd2a99166910 100644 --- a/enterprise/coderd/aibridge.go +++ b/enterprise/coderd/aibridge.go @@ -36,7 +36,7 @@ const ( // @Param after_id query string false "Cursor pagination after ID (cannot be used with offset)" // @Param offset query int false "Offset pagination (cannot be used with after_id)" // @Success 200 {object} codersdk.AIBridgeListInterceptionsResponse -// @Router /api/experimental/aibridge/interceptions [get] +// @Router /aibridge/interceptions [get] func (api *API) aiBridgeListInterceptions(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() apiKey := httpmw.APIKey(r) diff --git a/enterprise/coderd/aibridge_test.go b/enterprise/coderd/aibridge_test.go index 4198fdcf178ec..17e5df56fb65d 100644 --- a/enterprise/coderd/aibridge_test.go +++ b/enterprise/coderd/aibridge_test.go @@ -36,10 +36,10 @@ func TestAIBridgeListInterceptions(t *testing.T) { Features: license.Features{}, }, }) - experimentalClient := codersdk.NewExperimentalClient(client) ctx := testutil.Context(t, testutil.WaitLong) - _, err := experimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) + //nolint:gocritic // Owner role is irrelevant here. + _, err := client.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) var sdkErr *codersdk.Error require.ErrorAs(t, err, &sdkErr) require.Equal(t, http.StatusForbidden, sdkErr.StatusCode()) @@ -59,9 +59,9 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, }, }) - experimentalClient := codersdk.NewExperimentalClient(client) ctx := testutil.Context(t, testutil.WaitLong) - res, err := experimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) + //nolint:gocritic // Owner role is irrelevant here. + res, err := client.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) require.NoError(t, err) require.Empty(t, res.Results) }) @@ -79,7 +79,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, }, }) - experimentalClient := codersdk.NewExperimentalClient(client) ctx := testutil.Context(t, testutil.WaitLong) user1, err := client.User(ctx, codersdk.Me) @@ -140,7 +139,7 @@ func TestAIBridgeListInterceptions(t *testing.T) { i1SDK := db2sdk.AIBridgeInterception(i1, user1Visible, []database.AIBridgeTokenUsage{i1tok2, i1tok1}, []database.AIBridgeUserPrompt{i1up2, i1up1}, []database.AIBridgeToolUsage{i1tool2, i1tool1}) i2SDK := db2sdk.AIBridgeInterception(i2, user2Visible, nil, nil, nil) - res, err := experimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) + res, err := client.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) require.NoError(t, err) require.Len(t, res.Results, 2) require.Equal(t, i2SDK.ID, res.Results[0].ID) @@ -190,7 +189,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, }, }) - experimentalClient := codersdk.NewExperimentalClient(client) ctx := testutil.Context(t, testutil.WaitLong) allInterceptionIDs := make([]uuid.UUID, 0, 20) @@ -221,7 +219,7 @@ func TestAIBridgeListInterceptions(t *testing.T) { } // Try to fetch with an invalid limit. - res, err := experimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{ + res, err := client.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{ Pagination: codersdk.Pagination{ Limit: 1001, }, @@ -232,7 +230,7 @@ func TestAIBridgeListInterceptions(t *testing.T) { require.Empty(t, res.Results) // Try to fetch with both after_id and offset pagination. - res, err = experimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{ + res, err = client.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{ Pagination: codersdk.Pagination{ AfterID: allInterceptionIDs[0], Offset: 1, @@ -265,7 +263,7 @@ func TestAIBridgeListInterceptions(t *testing.T) { } else { pagination.Offset = len(interceptionIDs) } - res, err := experimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{ + res, err := client.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{ Pagination: pagination, }) require.NoError(t, err) @@ -305,11 +303,9 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, }, }) - adminExperimentalClient := codersdk.NewExperimentalClient(adminClient) ctx := testutil.Context(t, testutil.WaitLong) secondUserClient, secondUser := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID) - secondUserExperimentalClient := codersdk.NewExperimentalClient(secondUserClient) now := dbtime.Now() i1 := dbgen.AIBridgeInterception(t, db, database.InsertAIBridgeInterceptionParams{ @@ -322,7 +318,7 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, &now) // Admin can see all interceptions. - res, err := adminExperimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) + res, err := adminClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) require.NoError(t, err) require.EqualValues(t, 2, res.Count) require.Len(t, res.Results, 2) @@ -330,7 +326,7 @@ func TestAIBridgeListInterceptions(t *testing.T) { require.Equal(t, i2.ID, res.Results[1].ID) // Second user can only see their own interceptions. - res, err = secondUserExperimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) + res, err = secondUserClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{}) require.NoError(t, err) require.EqualValues(t, 1, res.Count) require.Len(t, res.Results, 1) @@ -350,7 +346,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, }, }) - experimentalClient := codersdk.NewExperimentalClient(client) ctx := testutil.Context(t, testutil.WaitLong) user1, err := client.User(ctx, codersdk.Me) @@ -500,7 +495,7 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) - res, err := experimentalClient.AIBridgeListInterceptions(ctx, tc.filter) + res, err := client.AIBridgeListInterceptions(ctx, tc.filter) require.NoError(t, err) require.EqualValues(t, len(tc.want), res.Count) // We just compare UUID strings for the sake of this test. @@ -530,7 +525,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, }, }) - experimentalClient := codersdk.NewExperimentalClient(client) // No need to insert any test data, we're just testing the filter // errors. @@ -587,7 +581,7 @@ func TestAIBridgeListInterceptions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) - res, err := experimentalClient.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{ + res, err := client.AIBridgeListInterceptions(ctx, codersdk.AIBridgeListInterceptionsFilter{ FilterQuery: tc.q, }) var sdkErr *codersdk.Error diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 1bd517d0c86b1..a4adb0479b96b 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -226,7 +226,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { return api.refreshEntitlements(ctx) } - api.AGPL.ExperimentalHandler.Group(func(r chi.Router) { + api.AGPL.APIHandler.Group(func(r chi.Router) { r.Route("/aibridge", func(r chi.Router) { r.Use(api.RequireFeatureMW(codersdk.FeatureAIBridge)) r.Group(func(r chi.Router) { @@ -243,7 +243,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { }) return } - http.StripPrefix("/api/experimental/aibridge", api.aibridgedHandler).ServeHTTP(rw, r) + http.StripPrefix("/api/v2/aibridge", api.aibridgedHandler).ServeHTTP(rw, r) }) }) }) From dcfd6d6f730c9560b8558680cf9577437dbdaeee Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 29 Oct 2025 07:36:08 -0600 Subject: [PATCH 008/255] chore: graduate `aibridge` cli out of experimental (#20524) --- docs/manifest.json | 15 ++++ docs/reference/cli/aibridge.md | 16 +++++ docs/reference/cli/aibridge_interceptions.md | 16 +++++ .../cli/aibridge_interceptions_list.md | 69 +++++++++++++++++++ docs/reference/cli/index.md | 1 + .../cli/{exp_aibridge.go => aibridge.go} | 3 +- ...{exp_aibridge_test.go => aibridge_test.go} | 3 - enterprise/cli/root.go | 7 +- enterprise/cli/testdata/coder_--help.golden | 1 + .../cli/testdata/coder_aibridge_--help.golden | 12 ++++ ...coder_aibridge_interceptions_--help.golden | 12 ++++ ..._aibridge_interceptions_list_--help.golden | 37 ++++++++++ 12 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 docs/reference/cli/aibridge.md create mode 100644 docs/reference/cli/aibridge_interceptions.md create mode 100644 docs/reference/cli/aibridge_interceptions_list.md rename enterprise/cli/{exp_aibridge.go => aibridge.go} (97%) rename enterprise/cli/{exp_aibridge_test.go => aibridge_test.go} (99%) create mode 100644 enterprise/cli/testdata/coder_aibridge_--help.golden create mode 100644 enterprise/cli/testdata/coder_aibridge_interceptions_--help.golden create mode 100644 enterprise/cli/testdata/coder_aibridge_interceptions_list_--help.golden diff --git a/docs/manifest.json b/docs/manifest.json index 78a0d38ec949d..57711406c87d7 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1180,6 +1180,21 @@ "path": "./reference/cli/index.md", "icon_path": "./images/icons/terminal.svg", "children": [ + { + "title": "aibridge", + "description": "Manage AIBridge.", + "path": "reference/cli/aibridge.md" + }, + { + "title": "aibridge interceptions", + "description": "Manage AIBridge interceptions.", + "path": "reference/cli/aibridge_interceptions.md" + }, + { + "title": "aibridge interceptions list", + "description": "List AIBridge interceptions as JSON.", + "path": "reference/cli/aibridge_interceptions_list.md" + }, { "title": "autoupdate", "description": "Toggle auto-update policy for a workspace", diff --git a/docs/reference/cli/aibridge.md b/docs/reference/cli/aibridge.md new file mode 100644 index 0000000000000..7b16c5cdc87a8 --- /dev/null +++ b/docs/reference/cli/aibridge.md @@ -0,0 +1,16 @@ + +# aibridge + +Manage AIBridge. + +## Usage + +```console +coder aibridge +``` + +## Subcommands + +| Name | Purpose | +|-----------------------------------------------------------|--------------------------------| +| [interceptions](./aibridge_interceptions.md) | Manage AIBridge interceptions. | diff --git a/docs/reference/cli/aibridge_interceptions.md b/docs/reference/cli/aibridge_interceptions.md new file mode 100644 index 0000000000000..9cfb3d45a74ea --- /dev/null +++ b/docs/reference/cli/aibridge_interceptions.md @@ -0,0 +1,16 @@ + +# aibridge interceptions + +Manage AIBridge interceptions. + +## Usage + +```console +coder aibridge interceptions +``` + +## Subcommands + +| Name | Purpose | +|-------------------------------------------------------|--------------------------------------| +| [list](./aibridge_interceptions_list.md) | List AIBridge interceptions as JSON. | diff --git a/docs/reference/cli/aibridge_interceptions_list.md b/docs/reference/cli/aibridge_interceptions_list.md new file mode 100644 index 0000000000000..7e86cd4968e33 --- /dev/null +++ b/docs/reference/cli/aibridge_interceptions_list.md @@ -0,0 +1,69 @@ + +# aibridge interceptions list + +List AIBridge interceptions as JSON. + +## Usage + +```console +coder aibridge interceptions list [flags] +``` + +## Options + +### --initiator + +| | | +|------|---------------------| +| Type | string | + +Only return interceptions initiated by this user. Accepts a user ID, username, or "me". + +### --started-before + +| | | +|------|---------------------| +| Type | string | + +Only return interceptions started before this time. Must be after 'started-after' if set. Accepts a time in the RFC 3339 format, e.g. "2006-01-02T15:04:05Z07:00". + +### --started-after + +| | | +|------|---------------------| +| Type | string | + +Only return interceptions started after this time. Must be before 'started-before' if set. Accepts a time in the RFC 3339 format, e.g. "2006-01-02T15:04:05Z07:00". + +### --provider + +| | | +|------|---------------------| +| Type | string | + +Only return interceptions from this provider. + +### --model + +| | | +|------|---------------------| +| Type | string | + +Only return interceptions from this model. + +### --after-id + +| | | +|------|---------------------| +| Type | string | + +The ID of the last result on the previous page to use as a pagination cursor. + +### --limit + +| | | +|---------|------------------| +| Type | int | +| Default | 100 | + +The limit of results to return. Must be between 1 and 1000. diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md index c298f8bcb61a2..c1410b4599977 100644 --- a/docs/reference/cli/index.md +++ b/docs/reference/cli/index.md @@ -68,6 +68,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr | [groups](./groups.md) | Manage groups | | [prebuilds](./prebuilds.md) | Manage Coder prebuilds | | [external-workspaces](./external-workspaces.md) | Create or manage external workspaces | +| [aibridge](./aibridge.md) | Manage AIBridge. | ## Options diff --git a/enterprise/cli/exp_aibridge.go b/enterprise/cli/aibridge.go similarity index 97% rename from enterprise/cli/exp_aibridge.go rename to enterprise/cli/aibridge.go index 722f7bf239223..90953b6aa2bf2 100644 --- a/enterprise/cli/exp_aibridge.go +++ b/enterprise/cli/aibridge.go @@ -134,8 +134,7 @@ func (r *RootCmd) aibridgeInterceptionsList() *serpent.Command { return xerrors.Errorf("limit value must be between 1 and %d", maxInterceptionsLimit) } - expCli := codersdk.NewExperimentalClient(client) - resp, err := expCli.AIBridgeListInterceptions(inv.Context(), codersdk.AIBridgeListInterceptionsFilter{ + resp, err := client.AIBridgeListInterceptions(inv.Context(), codersdk.AIBridgeListInterceptionsFilter{ Pagination: codersdk.Pagination{ AfterID: afterID, // #nosec G115 - Checked above. diff --git a/enterprise/cli/exp_aibridge_test.go b/enterprise/cli/aibridge_test.go similarity index 99% rename from enterprise/cli/exp_aibridge_test.go rename to enterprise/cli/aibridge_test.go index db1cf43629626..a5b48a14e1c38 100644 --- a/enterprise/cli/exp_aibridge_test.go +++ b/enterprise/cli/aibridge_test.go @@ -54,7 +54,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, nil) args := []string{ - "exp", "aibridge", "interceptions", "list", @@ -135,7 +134,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, nil) args := []string{ - "exp", "aibridge", "interceptions", "list", @@ -189,7 +187,6 @@ func TestAIBridgeListInterceptions(t *testing.T) { }, nil) args := []string{ - "exp", "aibridge", "interceptions", "list", diff --git a/enterprise/cli/root.go b/enterprise/cli/root.go index 3cec11970369e..78858ef48da7b 100644 --- a/enterprise/cli/root.go +++ b/enterprise/cli/root.go @@ -25,13 +25,12 @@ func (r *RootCmd) enterpriseOnly() []*serpent.Command { r.prebuilds(), r.provisionerd(), r.externalWorkspaces(), + r.aibridge(), } } -func (r *RootCmd) enterpriseExperimental() []*serpent.Command { - return []*serpent.Command{ - r.aibridge(), - } +func (*RootCmd) enterpriseExperimental() []*serpent.Command { + return []*serpent.Command{} } func (r *RootCmd) EnterpriseSubcommands() []*serpent.Command { diff --git a/enterprise/cli/testdata/coder_--help.golden b/enterprise/cli/testdata/coder_--help.golden index ddb44f78ae524..8424ccac923a2 100644 --- a/enterprise/cli/testdata/coder_--help.golden +++ b/enterprise/cli/testdata/coder_--help.golden @@ -14,6 +14,7 @@ USAGE: $ coder templates init SUBCOMMANDS: + aibridge Manage AIBridge. external-workspaces Create or manage external workspaces features List Enterprise features groups Manage groups diff --git a/enterprise/cli/testdata/coder_aibridge_--help.golden b/enterprise/cli/testdata/coder_aibridge_--help.golden new file mode 100644 index 0000000000000..d005ae429ad50 --- /dev/null +++ b/enterprise/cli/testdata/coder_aibridge_--help.golden @@ -0,0 +1,12 @@ +coder v0.0.0-devel + +USAGE: + coder aibridge + + Manage AIBridge. + +SUBCOMMANDS: + interceptions Manage AIBridge interceptions. + +——— +Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_aibridge_interceptions_--help.golden b/enterprise/cli/testdata/coder_aibridge_interceptions_--help.golden new file mode 100644 index 0000000000000..1f3b3af5ad3d3 --- /dev/null +++ b/enterprise/cli/testdata/coder_aibridge_interceptions_--help.golden @@ -0,0 +1,12 @@ +coder v0.0.0-devel + +USAGE: + coder aibridge interceptions + + Manage AIBridge interceptions. + +SUBCOMMANDS: + list List AIBridge interceptions as JSON. + +——— +Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_aibridge_interceptions_list_--help.golden b/enterprise/cli/testdata/coder_aibridge_interceptions_list_--help.golden new file mode 100644 index 0000000000000..c98fd0019a45a --- /dev/null +++ b/enterprise/cli/testdata/coder_aibridge_interceptions_list_--help.golden @@ -0,0 +1,37 @@ +coder v0.0.0-devel + +USAGE: + coder aibridge interceptions list [flags] + + List AIBridge interceptions as JSON. + +OPTIONS: + --after-id string + The ID of the last result on the previous page to use as a pagination + cursor. + + --initiator string + Only return interceptions initiated by this user. Accepts a user ID, + username, or "me". + + --limit int (default: 100) + The limit of results to return. Must be between 1 and 1000. + + --model string + Only return interceptions from this model. + + --provider string + Only return interceptions from this provider. + + --started-after string + Only return interceptions started after this time. Must be before + 'started-before' if set. Accepts a time in the RFC 3339 format, e.g. + "====[timestamp]=====07:00". + + --started-before string + Only return interceptions started before this time. Must be after + 'started-after' if set. Accepts a time in the RFC 3339 format, e.g. + "====[timestamp]=====07:00". + +——— +Run `coder --help` for a list of global options. From 2f886ce8d0b7ab6bf9ad25bab0ace72af1829a51 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 29 Oct 2025 07:43:33 -0600 Subject: [PATCH 009/255] chore: update docs (#20521) Updates AI Bridge docs to remove experiment details. --- docs/ai-coder/ai-bridge.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/ai-coder/ai-bridge.md b/docs/ai-coder/ai-bridge.md index c7cfbe7d85ea2..a993cee71319c 100644 --- a/docs/ai-coder/ai-bridge.md +++ b/docs/ai-coder/ai-bridge.md @@ -1,8 +1,5 @@ # AI Bridge -> [!NOTE] -> AI Bridge is currently an _experimental_ feature. - ![AI bridge diagram](../images/aibridge/aibridge_diagram.png) Bridge is a smart proxy for AI. It acts as a man-in-the-middle between your users' coding agents / IDEs @@ -45,17 +42,14 @@ Bridge runs inside the Coder control plane, requiring no separate compute to dep ### Activation -To enable this feature, activate the `aibridge` experiment using an environment variable or a CLI flag. -Additionally, you will need to enable Bridge explicitly: +You will need to enable AI Bridge explicitly: ```sh -CODER_EXPERIMENTS="aibridge" CODER_AIBRIDGE_ENABLED=true coder server +CODER_AIBRIDGE_ENABLED=true coder server # or -coder server --experiments=aibridge --aibridge-enabled=true +coder server --aibridge-enabled=true ``` -_If you have other experiments enabled, separate them by commas._ - ### Providers Bridge currently supports OpenAI and Anthropic APIs. @@ -89,8 +83,8 @@ Once AI Bridge is enabled on the server, your users need to configure their AI c The exact configuration method varies by client — some use environment variables, others use configuration files or UI settings: -- **OpenAI-compatible clients**: Set the base URL (commonly via the `OPENAI_BASE_URL` environment variable) to `https://coder.example.com/api/experimental/aibridge/openai/v1` -- **Anthropic-compatible clients**: Set the base URL (commonly via the `ANTHROPIC_BASE_URL` environment variable) to `https://coder.example.com/api/experimental/aibridge/anthropic` +- **OpenAI-compatible clients**: Set the base URL (commonly via the `OPENAI_BASE_URL` environment variable) to `https://coder.example.com/api/v2/aibridge/openai/v1` +- **Anthropic-compatible clients**: Set the base URL (commonly via the `ANTHROPIC_BASE_URL` environment variable) to `https://coder.example.com/api/v2/aibridge/anthropic` Replace `coder.example.com` with your actual Coder deployment URL. @@ -133,7 +127,7 @@ All of these records are associated to an "interception" record, which maps 1:1 These logs can be used to determine usage patterns, track costs, and evaluate tooling adoption. -This data is currently accessible through the API and CLI (experimental), which we advise administrators export to their observability platform of choice. We've configured a Grafana dashboard to display Claude Code usage internally which can be imported as a starting point for your tooling adoption metrics. +This data is currently accessible through the API and CLI, which we advise administrators export to their observability platform of choice. We've configured a Grafana dashboard to display Claude Code usage internally which can be imported as a starting point for your tooling adoption metrics. ![User Leaderboard](../images/aibridge/grafana_user_leaderboard.png) From dd28eef5b4e2b2c1aa17cc007fd7238af9de74b3 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 29 Oct 2025 07:53:34 -0600 Subject: [PATCH 010/255] chore: update dogfood template to use new aibridge endpoint (#20525) Also updating Nix to 2.28.5 since the previous version 404s. Closes https://github.com/coder/internal/issues/1105 --- dogfood/coder/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dogfood/coder/main.tf b/dogfood/coder/main.tf index 05ec4a6a2e975..37a53349bb903 100644 --- a/dogfood/coder/main.tf +++ b/dogfood/coder/main.tf @@ -479,7 +479,7 @@ resource "coder_agent" "dev" { dir = local.repo_dir env = { OIDC_TOKEN : data.coder_workspace_owner.me.oidc_access_token, - ANTHROPIC_BASE_URL : "https://dev.coder.com/api/experimental/aibridge/anthropic", + ANTHROPIC_BASE_URL : "https://dev.coder.com/api/v2/aibridge/anthropic", ANTHROPIC_AUTH_TOKEN : data.coder_workspace_owner.me.session_token } startup_script_behavior = "blocking" From 7e8fcb4b0f3a7413ff10941e0e35b1f807566a04 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Wed, 29 Oct 2025 14:24:29 +0000 Subject: [PATCH 011/255] perf: optimize prebuilds membership reconciliation to check orgs not presets (#20493) ## Description The membership reconciliation ensures the prebuilds system user is a member of all organizations with prebuilds configured. To support prebuilds quota management, each organization must have a prebuilds group that the system user belongs to. ## Problem Previously, membership reconciliation iterated over all presets to check and update membership status. This meant database queries `GetGroupByOrgAndName` and `InsertGroupMember` were executed for each preset. Since presets are unique combinations of `(organization, template, template version, preset)`, this resulted in several redundant checks for the same organization. In dogfood, `InsertGroupMember` was called thousands of times per day, even though memberships were already configured ([internal Grafana dashboard link](https://grafana.dev.coder.com/goto/46MZ1UgDg?orgId=1)) Screenshot 2025-10-28 at 16 01 36 ## Solution This PR introduces `GetOrganizationsWithPrebuildStatus`, a single query that returns: * All unique organizations with prebuilds configured * Whether the prebuilds user is a member of each organization * Whether the prebuilds group exists in each organization * Whether the prebuilds user is in the prebuilds group The membership reconciliation logic now: * Fetches status for all organizations in one query * Only performs inserts for organizations missing required memberships or groups * Safely handles concurrent operations via unique constraint violations * This reduces database load from `O(presets)` to `O(organizations)` per reconciliation loop, with a single read query when everything is configured. ## Changes * Add `GetOrganizationsWithPrebuildStatus` SQL query * Update `membership.ReconcileAll` to use organization-based reconciliation instead of preset-based * Update tests to reflect new behavior Related to internal thread: https://codercom.slack.com/archives/C07GRNNRW03/p1760535570381369 --- coderd/database/dbauthz/dbauthz.go | 7 + coderd/database/dbauthz/dbauthz_test.go | 8 + coderd/database/dbmetrics/querymetrics.go | 7 + coderd/database/dbmock/dbmock.go | 15 + coderd/database/querier.go | 3 + coderd/database/queries.sql.go | 87 ++++++ coderd/database/queries/prebuilds.sql | 44 +++ enterprise/coderd/prebuilds/membership.go | 148 +++++----- .../coderd/prebuilds/membership_test.go | 278 +++++++++--------- enterprise/coderd/prebuilds/reconcile.go | 12 +- 10 files changed, 397 insertions(+), 212 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 2081fbf5a8aac..95937a77d2075 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2648,6 +2648,13 @@ func (q *querier) GetOrganizationsByUserID(ctx context.Context, userID database. return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetOrganizationsByUserID)(ctx, userID) } +func (q *querier) GetOrganizationsWithPrebuildStatus(ctx context.Context, arg database.GetOrganizationsWithPrebuildStatusParams) ([]database.GetOrganizationsWithPrebuildStatusRow, error) { + if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceOrganization.All()); err != nil { + return nil, err + } + return q.db.GetOrganizationsWithPrebuildStatus(ctx, arg) +} + func (q *querier) GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]database.ParameterSchema, error) { version, err := q.db.GetTemplateVersionByJobID(ctx, jobID) if err != nil { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 63226271f7fa0..32c951fb5c20b 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -3759,6 +3759,14 @@ func (s *MethodTestSuite) TestPrebuilds() { dbm.EXPECT().GetPrebuildMetrics(gomock.Any()).Return([]database.GetPrebuildMetricsRow{}, nil).AnyTimes() check.Args().Asserts(rbac.ResourceWorkspace.All(), policy.ActionRead) })) + s.Run("GetOrganizationsWithPrebuildStatus", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { + arg := database.GetOrganizationsWithPrebuildStatusParams{ + UserID: uuid.New(), + GroupName: "test", + } + dbm.EXPECT().GetOrganizationsWithPrebuildStatus(gomock.Any(), arg).Return([]database.GetOrganizationsWithPrebuildStatusRow{}, nil).AnyTimes() + check.Args(arg).Asserts(rbac.ResourceOrganization.All(), policy.ActionRead) + })) s.Run("GetPrebuildsSettings", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) { dbm.EXPECT().GetPrebuildsSettings(gomock.Any()).Return("{}", nil).AnyTimes() check.Args().Asserts() diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 1b36a3fa987f2..252f6f9b5ad09 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -1243,6 +1243,13 @@ func (m queryMetricsStore) GetOrganizationsByUserID(ctx context.Context, userID return organizations, err } +func (m queryMetricsStore) GetOrganizationsWithPrebuildStatus(ctx context.Context, arg database.GetOrganizationsWithPrebuildStatusParams) ([]database.GetOrganizationsWithPrebuildStatusRow, error) { + start := time.Now() + r0, r1 := m.s.GetOrganizationsWithPrebuildStatus(ctx, arg) + m.queryLatencies.WithLabelValues("GetOrganizationsWithPrebuildStatus").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m queryMetricsStore) GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]database.ParameterSchema, error) { start := time.Now() schemas, err := m.s.GetParameterSchemasByJobID(ctx, jobID) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 0ecd0191cbce1..af89a987a3203 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -2622,6 +2622,21 @@ func (mr *MockStoreMockRecorder) GetOrganizationsByUserID(ctx, arg any) *gomock. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrganizationsByUserID", reflect.TypeOf((*MockStore)(nil).GetOrganizationsByUserID), ctx, arg) } +// GetOrganizationsWithPrebuildStatus mocks base method. +func (m *MockStore) GetOrganizationsWithPrebuildStatus(ctx context.Context, arg database.GetOrganizationsWithPrebuildStatusParams) ([]database.GetOrganizationsWithPrebuildStatusRow, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOrganizationsWithPrebuildStatus", ctx, arg) + ret0, _ := ret[0].([]database.GetOrganizationsWithPrebuildStatusRow) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOrganizationsWithPrebuildStatus indicates an expected call of GetOrganizationsWithPrebuildStatus. +func (mr *MockStoreMockRecorder) GetOrganizationsWithPrebuildStatus(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrganizationsWithPrebuildStatus", reflect.TypeOf((*MockStore)(nil).GetOrganizationsWithPrebuildStatus), ctx, arg) +} + // GetParameterSchemasByJobID mocks base method. func (m *MockStore) GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]database.ParameterSchema, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 2aa1cb8650051..b1a450939834d 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -269,6 +269,9 @@ type sqlcQuerier interface { GetOrganizationResourceCountByID(ctx context.Context, organizationID uuid.UUID) (GetOrganizationResourceCountByIDRow, error) GetOrganizations(ctx context.Context, arg GetOrganizationsParams) ([]Organization, error) GetOrganizationsByUserID(ctx context.Context, arg GetOrganizationsByUserIDParams) ([]Organization, error) + // GetOrganizationsWithPrebuildStatus returns organizations with prebuilds configured and their + // membership status for the prebuilds system user (org membership, group existence, group membership). + GetOrganizationsWithPrebuildStatus(ctx context.Context, arg GetOrganizationsWithPrebuildStatusParams) ([]GetOrganizationsWithPrebuildStatusRow, error) GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]ParameterSchema, error) GetPrebuildMetrics(ctx context.Context) ([]GetPrebuildMetricsRow, error) GetPrebuildsSettings(ctx context.Context) (string, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 2869316266a20..de5a4d02338bc 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -8285,6 +8285,93 @@ func (q *sqlQuerier) FindMatchingPresetID(ctx context.Context, arg FindMatchingP return template_version_preset_id, err } +const getOrganizationsWithPrebuildStatus = `-- name: GetOrganizationsWithPrebuildStatus :many +WITH orgs_with_prebuilds AS ( + -- Get unique organizations that have presets with prebuilds configured + SELECT DISTINCT o.id, o.name + FROM organizations o + INNER JOIN templates t ON t.organization_id = o.id + INNER JOIN template_versions tv ON tv.template_id = t.id + INNER JOIN template_version_presets tvp ON tvp.template_version_id = tv.id + WHERE tvp.desired_instances IS NOT NULL +), +prebuild_user_membership AS ( + -- Check if the user is a member of the organizations + SELECT om.organization_id + FROM organization_members om + INNER JOIN orgs_with_prebuilds owp ON owp.id = om.organization_id + WHERE om.user_id = $1::uuid +), +prebuild_groups AS ( + -- Check if the organizations have the prebuilds group + SELECT g.organization_id, g.id as group_id + FROM groups g + INNER JOIN orgs_with_prebuilds owp ON owp.id = g.organization_id + WHERE g.name = $2::text +), +prebuild_group_membership AS ( + -- Check if the user is in the prebuilds group + SELECT pg.organization_id + FROM prebuild_groups pg + INNER JOIN group_members gm ON gm.group_id = pg.group_id + WHERE gm.user_id = $1::uuid +) +SELECT + owp.id AS organization_id, + owp.name AS organization_name, + (pum.organization_id IS NOT NULL)::boolean AS has_prebuild_user, + pg.group_id AS prebuilds_group_id, + (pgm.organization_id IS NOT NULL)::boolean AS has_prebuild_user_in_group +FROM orgs_with_prebuilds owp +LEFT JOIN prebuild_groups pg ON pg.organization_id = owp.id +LEFT JOIN prebuild_user_membership pum ON pum.organization_id = owp.id +LEFT JOIN prebuild_group_membership pgm ON pgm.organization_id = owp.id +` + +type GetOrganizationsWithPrebuildStatusParams struct { + UserID uuid.UUID `db:"user_id" json:"user_id"` + GroupName string `db:"group_name" json:"group_name"` +} + +type GetOrganizationsWithPrebuildStatusRow struct { + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + OrganizationName string `db:"organization_name" json:"organization_name"` + HasPrebuildUser bool `db:"has_prebuild_user" json:"has_prebuild_user"` + PrebuildsGroupID uuid.NullUUID `db:"prebuilds_group_id" json:"prebuilds_group_id"` + HasPrebuildUserInGroup bool `db:"has_prebuild_user_in_group" json:"has_prebuild_user_in_group"` +} + +// GetOrganizationsWithPrebuildStatus returns organizations with prebuilds configured and their +// membership status for the prebuilds system user (org membership, group existence, group membership). +func (q *sqlQuerier) GetOrganizationsWithPrebuildStatus(ctx context.Context, arg GetOrganizationsWithPrebuildStatusParams) ([]GetOrganizationsWithPrebuildStatusRow, error) { + rows, err := q.db.QueryContext(ctx, getOrganizationsWithPrebuildStatus, arg.UserID, arg.GroupName) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetOrganizationsWithPrebuildStatusRow + for rows.Next() { + var i GetOrganizationsWithPrebuildStatusRow + if err := rows.Scan( + &i.OrganizationID, + &i.OrganizationName, + &i.HasPrebuildUser, + &i.PrebuildsGroupID, + &i.HasPrebuildUserInGroup, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getPrebuildMetrics = `-- name: GetPrebuildMetrics :many SELECT t.name as template_name, diff --git a/coderd/database/queries/prebuilds.sql b/coderd/database/queries/prebuilds.sql index 7c060971efba5..ae70593b269d9 100644 --- a/coderd/database/queries/prebuilds.sql +++ b/coderd/database/queries/prebuilds.sql @@ -327,3 +327,47 @@ SET FROM jobs_to_cancel WHERE provisioner_jobs.id = jobs_to_cancel.id RETURNING jobs_to_cancel.id, jobs_to_cancel.workspace_id, jobs_to_cancel.template_id, jobs_to_cancel.template_version_preset_id; + +-- name: GetOrganizationsWithPrebuildStatus :many +-- GetOrganizationsWithPrebuildStatus returns organizations with prebuilds configured and their +-- membership status for the prebuilds system user (org membership, group existence, group membership). +WITH orgs_with_prebuilds AS ( + -- Get unique organizations that have presets with prebuilds configured + SELECT DISTINCT o.id, o.name + FROM organizations o + INNER JOIN templates t ON t.organization_id = o.id + INNER JOIN template_versions tv ON tv.template_id = t.id + INNER JOIN template_version_presets tvp ON tvp.template_version_id = tv.id + WHERE tvp.desired_instances IS NOT NULL +), +prebuild_user_membership AS ( + -- Check if the user is a member of the organizations + SELECT om.organization_id + FROM organization_members om + INNER JOIN orgs_with_prebuilds owp ON owp.id = om.organization_id + WHERE om.user_id = @user_id::uuid +), +prebuild_groups AS ( + -- Check if the organizations have the prebuilds group + SELECT g.organization_id, g.id as group_id + FROM groups g + INNER JOIN orgs_with_prebuilds owp ON owp.id = g.organization_id + WHERE g.name = @group_name::text +), +prebuild_group_membership AS ( + -- Check if the user is in the prebuilds group + SELECT pg.organization_id + FROM prebuild_groups pg + INNER JOIN group_members gm ON gm.group_id = pg.group_id + WHERE gm.user_id = @user_id::uuid +) +SELECT + owp.id AS organization_id, + owp.name AS organization_name, + (pum.organization_id IS NOT NULL)::boolean AS has_prebuild_user, + pg.group_id AS prebuilds_group_id, + (pgm.organization_id IS NOT NULL)::boolean AS has_prebuild_user_in_group +FROM orgs_with_prebuilds owp +LEFT JOIN prebuild_groups pg ON pg.organization_id = owp.id +LEFT JOIN prebuild_user_membership pum ON pum.organization_id = owp.id +LEFT JOIN prebuild_group_membership pgm ON pgm.organization_id = owp.id; diff --git a/enterprise/coderd/prebuilds/membership.go b/enterprise/coderd/prebuilds/membership.go index f843d33f7f106..9436f68737d4a 100644 --- a/enterprise/coderd/prebuilds/membership.go +++ b/enterprise/coderd/prebuilds/membership.go @@ -2,12 +2,13 @@ package prebuilds import ( "context" - "database/sql" "errors" "github.com/google/uuid" "golang.org/x/xerrors" + "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/quartz" ) @@ -21,114 +22,117 @@ const ( // organizations for which prebuilt workspaces are requested. This is necessary because our data model requires that such // prebuilt workspaces belong to a member of the organization of their eventual claimant. type StoreMembershipReconciler struct { - store database.Store - clock quartz.Clock + store database.Store + clock quartz.Clock + logger slog.Logger } -func NewStoreMembershipReconciler(store database.Store, clock quartz.Clock) StoreMembershipReconciler { +func NewStoreMembershipReconciler(store database.Store, clock quartz.Clock, logger slog.Logger) StoreMembershipReconciler { return StoreMembershipReconciler{ - store: store, - clock: clock, + store: store, + clock: clock, + logger: logger, } } -// ReconcileAll compares the current organization and group memberships of a user to the memberships required -// in order to create prebuilt workspaces. If the user in question is not yet a member of an organization that -// needs prebuilt workspaces, ReconcileAll will create the membership required. +// ReconcileAll ensures the prebuilds system user has the necessary memberships to create prebuilt workspaces. +// For each organization with prebuilds configured, it ensures: +// * The user is a member of the organization +// * A group exists with quota 0 +// * The user is a member of that group // -// To facilitate quota management, ReconcileAll will ensure: -// * the existence of a group (defined by PrebuiltWorkspacesGroupName) in each organization that needs prebuilt workspaces -// * that the prebuilds system user belongs to the group in each organization that needs prebuilt workspaces -// * that the group has a quota of 0 by default, which users can adjust based on their needs. +// Unique constraint violations are safely ignored (concurrent creation). // // ReconcileAll does not have an opinion on transaction or lock management. These responsibilities are left to the caller. -func (s StoreMembershipReconciler) ReconcileAll(ctx context.Context, userID uuid.UUID, presets []database.GetTemplatePresetsWithPrebuildsRow) error { - organizationMemberships, err := s.store.GetOrganizationsByUserID(ctx, database.GetOrganizationsByUserIDParams{ - UserID: userID, - Deleted: sql.NullBool{ - Bool: false, - Valid: true, - }, +func (s StoreMembershipReconciler) ReconcileAll(ctx context.Context, userID uuid.UUID, groupName string) error { + orgStatuses, err := s.store.GetOrganizationsWithPrebuildStatus(ctx, database.GetOrganizationsWithPrebuildStatusParams{ + UserID: userID, + GroupName: groupName, }) if err != nil { - return xerrors.Errorf("determine prebuild organization membership: %w", err) - } - - orgMemberships := make(map[uuid.UUID]struct{}, 0) - defaultOrg, err := s.store.GetDefaultOrganization(ctx) - if err != nil { - return xerrors.Errorf("get default organization: %w", err) - } - orgMemberships[defaultOrg.ID] = struct{}{} - for _, o := range organizationMemberships { - orgMemberships[o.ID] = struct{}{} + return xerrors.Errorf("get organizations with prebuild status: %w", err) } var membershipInsertionErrors error - for _, preset := range presets { - _, alreadyOrgMember := orgMemberships[preset.OrganizationID] - if !alreadyOrgMember { - // Add the organization to our list of memberships regardless of potential failure below - // to avoid a retry that will probably be doomed anyway. - orgMemberships[preset.OrganizationID] = struct{}{} + for _, orgStatus := range orgStatuses { + s.logger.Debug(ctx, "organization prebuild status", + slog.F("organization_id", orgStatus.OrganizationID), + slog.F("organization_name", orgStatus.OrganizationName), + slog.F("has_prebuild_user", orgStatus.HasPrebuildUser), + slog.F("has_prebuild_group", orgStatus.PrebuildsGroupID.Valid), + slog.F("has_prebuild_user_in_group", orgStatus.HasPrebuildUserInGroup)) - // Insert the missing membership + // Add user to org if needed + if !orgStatus.HasPrebuildUser { _, err = s.store.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{ - OrganizationID: preset.OrganizationID, + OrganizationID: orgStatus.OrganizationID, UserID: userID, CreatedAt: s.clock.Now(), UpdatedAt: s.clock.Now(), Roles: []string{}, }) - if err != nil { - membershipInsertionErrors = errors.Join(membershipInsertionErrors, xerrors.Errorf("insert membership for prebuilt workspaces: %w", err)) + // Unique violation means organization membership was created after status check, safe to ignore. + if err != nil && !database.IsUniqueViolation(err) { + membershipInsertionErrors = errors.Join(membershipInsertionErrors, err) continue } - } - - // determine whether the org already has a prebuilds group - prebuildsGroupExists := true - prebuildsGroup, err := s.store.GetGroupByOrgAndName(ctx, database.GetGroupByOrgAndNameParams{ - OrganizationID: preset.OrganizationID, - Name: PrebuiltWorkspacesGroupName, - }) - if err != nil { - if !xerrors.Is(err, sql.ErrNoRows) { - membershipInsertionErrors = errors.Join(membershipInsertionErrors, xerrors.Errorf("get prebuilds group: %w", err)) - continue + if err == nil { + s.logger.Info(ctx, "added prebuilds user to organization", + slog.F("organization_id", orgStatus.OrganizationID), + slog.F("organization_name", orgStatus.OrganizationName), + slog.F("prebuilds_user", userID.String())) } - prebuildsGroupExists = false } - // if the prebuilds group does not exist, create it - if !prebuildsGroupExists { - // create a "prebuilds" group in the organization and add the system user to it - // this group will have a quota of 0 by default, which users can adjust based on their needs - prebuildsGroup, err = s.store.InsertGroup(ctx, database.InsertGroupParams{ + // Create group if it doesn't exist + var groupID uuid.UUID + if !orgStatus.PrebuildsGroupID.Valid { + // Group doesn't exist, create it + group, err := s.store.InsertGroup(ctx, database.InsertGroupParams{ ID: uuid.New(), Name: PrebuiltWorkspacesGroupName, DisplayName: PrebuiltWorkspacesGroupDisplayName, - OrganizationID: preset.OrganizationID, + OrganizationID: orgStatus.OrganizationID, AvatarURL: "", - QuotaAllowance: 0, // Default quota of 0, users should set this based on their needs + QuotaAllowance: 0, }) - if err != nil { - membershipInsertionErrors = errors.Join(membershipInsertionErrors, xerrors.Errorf("create prebuilds group: %w", err)) + // Unique violation means group was created after status check, safe to ignore. + if err != nil && !database.IsUniqueViolation(err) { + membershipInsertionErrors = errors.Join(membershipInsertionErrors, err) continue } + if err == nil { + s.logger.Info(ctx, "created prebuilds group in organization", + slog.F("organization_id", orgStatus.OrganizationID), + slog.F("organization_name", orgStatus.OrganizationName), + slog.F("prebuilds_group", group.ID.String())) + } + groupID = group.ID + } else { + // Group exists + groupID = orgStatus.PrebuildsGroupID.UUID } - // add the system user to the prebuilds group - err = s.store.InsertGroupMember(ctx, database.InsertGroupMemberParams{ - GroupID: prebuildsGroup.ID, - UserID: userID, - }) - if err != nil { - // ignore unique violation errors as the user might already be in the group - if !database.IsUniqueViolation(err) { - membershipInsertionErrors = errors.Join(membershipInsertionErrors, xerrors.Errorf("add system user to prebuilds group: %w", err)) + // Add user to group if needed + if !orgStatus.HasPrebuildUserInGroup { + err = s.store.InsertGroupMember(ctx, database.InsertGroupMemberParams{ + GroupID: groupID, + UserID: userID, + }) + // Unique violation means group membership was created after status check, safe to ignore. + if err != nil && !database.IsUniqueViolation(err) { + membershipInsertionErrors = errors.Join(membershipInsertionErrors, err) + continue + } + if err == nil { + s.logger.Info(ctx, "added prebuilds user to prebuilds group", + slog.F("organization_id", orgStatus.OrganizationID), + slog.F("organization_name", orgStatus.OrganizationName), + slog.F("prebuilds_user", userID.String()), + slog.F("prebuilds_group", groupID.String())) } } } + return membershipInsertionErrors } diff --git a/enterprise/coderd/prebuilds/membership_test.go b/enterprise/coderd/prebuilds/membership_test.go index 55d6557b12495..fe4ec26259889 100644 --- a/enterprise/coderd/prebuilds/membership_test.go +++ b/enterprise/coderd/prebuilds/membership_test.go @@ -7,16 +7,17 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/require" - "tailscale.com/types/ptr" - "github.com/coder/quartz" + "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" + "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/enterprise/coderd/prebuilds" "github.com/coder/coder/v2/testutil" + "github.com/coder/quartz" ) // TestReconcileAll verifies that StoreMembershipReconciler correctly updates membership @@ -26,169 +27,178 @@ func TestReconcileAll(t *testing.T) { clock := quartz.NewMock(t) - // Helper to build a minimal Preset row belonging to a given org. - newPresetRow := func(orgID uuid.UUID) database.GetTemplatePresetsWithPrebuildsRow { - return database.GetTemplatePresetsWithPrebuildsRow{ - ID: uuid.New(), - OrganizationID: orgID, - } - } - tests := []struct { name string - includePreset []bool + includePreset bool preExistingOrgMembership []bool preExistingGroup []bool preExistingGroupMembership []bool // Expected outcomes - expectOrgMembershipExists *bool - expectGroupExists *bool - expectUserInGroup *bool + expectOrgMembershipExists bool + expectGroupExists bool + expectUserInGroup bool }{ { name: "if there are no presets, membership reconciliation is a no-op", - includePreset: []bool{false}, + includePreset: false, preExistingOrgMembership: []bool{true, false}, preExistingGroup: []bool{true, false}, preExistingGroupMembership: []bool{true, false}, - expectOrgMembershipExists: ptr.To(false), - expectGroupExists: ptr.To(false), + expectOrgMembershipExists: false, + expectGroupExists: false, + expectUserInGroup: false, }, { name: "if there is a preset, then we should enforce org and group membership in all cases", - includePreset: []bool{true}, + includePreset: true, preExistingOrgMembership: []bool{true, false}, preExistingGroup: []bool{true, false}, preExistingGroupMembership: []bool{true, false}, - expectOrgMembershipExists: ptr.To(true), - expectGroupExists: ptr.To(true), - expectUserInGroup: ptr.To(true), + expectOrgMembershipExists: true, + expectGroupExists: true, + expectUserInGroup: true, }, } for _, tc := range tests { tc := tc - for _, includePreset := range tc.includePreset { - includePreset := includePreset - for _, preExistingOrgMembership := range tc.preExistingOrgMembership { - preExistingOrgMembership := preExistingOrgMembership - for _, preExistingGroup := range tc.preExistingGroup { - preExistingGroup := preExistingGroup - for _, preExistingGroupMembership := range tc.preExistingGroupMembership { - preExistingGroupMembership := preExistingGroupMembership - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - // nolint:gocritic // Reconciliation happens as prebuilds system user, not a human user. - ctx := dbauthz.AsPrebuildsOrchestrator(testutil.Context(t, testutil.WaitLong)) - _, db := coderdtest.NewWithDatabase(t, nil) - - defaultOrg, err := db.GetDefaultOrganization(ctx) - require.NoError(t, err) - - // introduce an unrelated organization to ensure that the membership reconciler doesn't interfere with it. - unrelatedOrg := dbgen.Organization(t, db, database.Organization{}) - targetOrg := dbgen.Organization(t, db, database.Organization{}) - - // Ensure membership to unrelated org. - dbgen.OrganizationMember(t, db, database.OrganizationMember{OrganizationID: unrelatedOrg.ID, UserID: database.PrebuildsSystemUserID}) - - if preExistingOrgMembership { - // System user already a member of both orgs. - dbgen.OrganizationMember(t, db, database.OrganizationMember{OrganizationID: targetOrg.ID, UserID: database.PrebuildsSystemUserID}) - } + includePreset := tc.includePreset + for _, preExistingOrgMembership := range tc.preExistingOrgMembership { + preExistingOrgMembership := preExistingOrgMembership + for _, preExistingGroup := range tc.preExistingGroup { + preExistingGroup := preExistingGroup + for _, preExistingGroupMembership := range tc.preExistingGroupMembership { + preExistingGroupMembership := preExistingGroupMembership + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // nolint:gocritic // Reconciliation happens as prebuilds system user, not a human user. + ctx := dbauthz.AsPrebuildsOrchestrator(testutil.Context(t, testutil.WaitLong)) + client, db := coderdtest.NewWithDatabase(t, nil) + owner := coderdtest.CreateFirstUser(t, client) + + defaultOrg, err := db.GetDefaultOrganization(ctx) + require.NoError(t, err) + + // Introduce an unrelated organization to ensure that the membership reconciler doesn't interfere with it. + unrelatedOrg := dbgen.Organization(t, db, database.Organization{}) + dbgen.OrganizationMember(t, db, database.OrganizationMember{OrganizationID: unrelatedOrg.ID, UserID: database.PrebuildsSystemUserID}) + + // Organization to test + targetOrg := dbgen.Organization(t, db, database.Organization{}) + + // Prebuilds system user is a member of the organization + if preExistingOrgMembership { + dbgen.OrganizationMember(t, db, database.OrganizationMember{OrganizationID: targetOrg.ID, UserID: database.PrebuildsSystemUserID}) + } + + // Organization has the prebuilds group + var prebuildsGroup database.Group + if preExistingGroup { + prebuildsGroup = dbgen.Group(t, db, database.Group{ + Name: prebuilds.PrebuiltWorkspacesGroupName, + DisplayName: prebuilds.PrebuiltWorkspacesGroupDisplayName, + OrganizationID: targetOrg.ID, + QuotaAllowance: 0, + }) - // Create pre-existing prebuilds group if required by test case - var prebuildsGroup database.Group - if preExistingGroup { - prebuildsGroup = dbgen.Group(t, db, database.Group{ - Name: prebuilds.PrebuiltWorkspacesGroupName, - DisplayName: prebuilds.PrebuiltWorkspacesGroupDisplayName, - OrganizationID: targetOrg.ID, - QuotaAllowance: 0, + // Add the system user to the group if required by test case + if preExistingGroupMembership { + dbgen.GroupMember(t, db, database.GroupMemberTable{ + GroupID: prebuildsGroup.ID, + UserID: database.PrebuildsSystemUserID, }) - - // Add the system user to the group if preExistingGroupMembership is true - if preExistingGroupMembership { - dbgen.GroupMember(t, db, database.GroupMemberTable{ - GroupID: prebuildsGroup.ID, - UserID: database.PrebuildsSystemUserID, - }) - } } - - presets := []database.GetTemplatePresetsWithPrebuildsRow{newPresetRow(unrelatedOrg.ID)} - if includePreset { - presets = append(presets, newPresetRow(targetOrg.ID)) - } - - // Verify memberships before reconciliation. - preReconcileMemberships, err := db.GetOrganizationsByUserID(ctx, database.GetOrganizationsByUserIDParams{ - UserID: database.PrebuildsSystemUserID, - }) - require.NoError(t, err) - expectedMembershipsBefore := []uuid.UUID{defaultOrg.ID, unrelatedOrg.ID} - if preExistingOrgMembership { - expectedMembershipsBefore = append(expectedMembershipsBefore, targetOrg.ID) - } - require.ElementsMatch(t, expectedMembershipsBefore, extractOrgIDs(preReconcileMemberships)) - - // Reconcile - reconciler := prebuilds.NewStoreMembershipReconciler(db, clock) - require.NoError(t, reconciler.ReconcileAll(ctx, database.PrebuildsSystemUserID, presets)) - - // Verify memberships after reconciliation. - postReconcileMemberships, err := db.GetOrganizationsByUserID(ctx, database.GetOrganizationsByUserIDParams{ - UserID: database.PrebuildsSystemUserID, - }) + } + + // Setup unrelated org preset + dbfake.TemplateVersion(t, db).Seed(database.TemplateVersion{ + OrganizationID: unrelatedOrg.ID, + CreatedBy: owner.UserID, + }).Preset(database.TemplateVersionPreset{ + DesiredInstances: sql.NullInt32{ + Int32: 1, + Valid: true, + }, + }).Do() + + // Setup target org preset + dbfake.TemplateVersion(t, db).Seed(database.TemplateVersion{ + OrganizationID: targetOrg.ID, + CreatedBy: owner.UserID, + }).Preset(database.TemplateVersionPreset{ + DesiredInstances: sql.NullInt32{ + Int32: 0, + Valid: includePreset, + }, + }).Do() + + // Verify memberships before reconciliation. + preReconcileMemberships, err := db.GetOrganizationsByUserID(ctx, database.GetOrganizationsByUserIDParams{ + UserID: database.PrebuildsSystemUserID, + }) + require.NoError(t, err) + expectedMembershipsBefore := []uuid.UUID{defaultOrg.ID, unrelatedOrg.ID} + if preExistingOrgMembership { + expectedMembershipsBefore = append(expectedMembershipsBefore, targetOrg.ID) + } + require.ElementsMatch(t, expectedMembershipsBefore, extractOrgIDs(preReconcileMemberships)) + + // Reconcile + reconciler := prebuilds.NewStoreMembershipReconciler(db, clock, slogtest.Make(t, nil)) + require.NoError(t, reconciler.ReconcileAll(ctx, database.PrebuildsSystemUserID, prebuilds.PrebuiltWorkspacesGroupName)) + + // Verify memberships after reconciliation. + postReconcileMemberships, err := db.GetOrganizationsByUserID(ctx, database.GetOrganizationsByUserIDParams{ + UserID: database.PrebuildsSystemUserID, + }) + require.NoError(t, err) + expectedMembershipsAfter := expectedMembershipsBefore + if !preExistingOrgMembership && tc.expectOrgMembershipExists { + expectedMembershipsAfter = append(expectedMembershipsAfter, targetOrg.ID) + } + require.ElementsMatch(t, expectedMembershipsAfter, extractOrgIDs(postReconcileMemberships)) + + // Verify prebuilds group behavior based on expected outcomes + prebuildsGroup, err = db.GetGroupByOrgAndName(ctx, database.GetGroupByOrgAndNameParams{ + OrganizationID: targetOrg.ID, + Name: prebuilds.PrebuiltWorkspacesGroupName, + }) + if tc.expectGroupExists { require.NoError(t, err) - expectedMembershipsAfter := expectedMembershipsBefore - if !preExistingOrgMembership && tc.expectOrgMembershipExists != nil && *tc.expectOrgMembershipExists { - expectedMembershipsAfter = append(expectedMembershipsAfter, targetOrg.ID) - } - require.ElementsMatch(t, expectedMembershipsAfter, extractOrgIDs(postReconcileMemberships)) - - // Verify prebuilds group behavior based on expected outcomes - prebuildsGroup, err = db.GetGroupByOrgAndName(ctx, database.GetGroupByOrgAndNameParams{ - OrganizationID: targetOrg.ID, - Name: prebuilds.PrebuiltWorkspacesGroupName, - }) - if tc.expectGroupExists != nil && *tc.expectGroupExists { + require.Equal(t, prebuilds.PrebuiltWorkspacesGroupName, prebuildsGroup.Name) + require.Equal(t, prebuilds.PrebuiltWorkspacesGroupDisplayName, prebuildsGroup.DisplayName) + require.Equal(t, int32(0), prebuildsGroup.QuotaAllowance) // Default quota should be 0 + + if tc.expectUserInGroup { + // Check that the system user is a member of the prebuilds group + groupMembers, err := db.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams{ + GroupID: prebuildsGroup.ID, + IncludeSystem: true, + }) require.NoError(t, err) - require.Equal(t, prebuilds.PrebuiltWorkspacesGroupName, prebuildsGroup.Name) - require.Equal(t, prebuilds.PrebuiltWorkspacesGroupDisplayName, prebuildsGroup.DisplayName) - require.Equal(t, int32(0), prebuildsGroup.QuotaAllowance) // Default quota should be 0 - - if tc.expectUserInGroup != nil && *tc.expectUserInGroup { - // Check that the system user is a member of the prebuilds group - groupMembers, err := db.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams{ - GroupID: prebuildsGroup.ID, - IncludeSystem: true, - }) - require.NoError(t, err) - require.Len(t, groupMembers, 1) - require.Equal(t, database.PrebuildsSystemUserID, groupMembers[0].UserID) - } - - // If no preset exists, then we do not enforce group membership: - if tc.expectUserInGroup != nil && !*tc.expectUserInGroup { - // Check that the system user is NOT a member of the prebuilds group - groupMembers, err := db.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams{ - GroupID: prebuildsGroup.ID, - IncludeSystem: true, - }) - require.NoError(t, err) - require.Len(t, groupMembers, 0) - } + require.Len(t, groupMembers, 1) + require.Equal(t, database.PrebuildsSystemUserID, groupMembers[0].UserID) } - if !preExistingGroup && tc.expectGroupExists != nil && !*tc.expectGroupExists { - // Verify that no prebuilds group exists - require.Error(t, err) - require.True(t, errors.Is(err, sql.ErrNoRows)) + // If no preset exists, then we do not enforce group membership: + if !tc.expectUserInGroup { + // Check that the system user is NOT a member of the prebuilds group + groupMembers, err := db.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams{ + GroupID: prebuildsGroup.ID, + IncludeSystem: true, + }) + require.NoError(t, err) + require.Len(t, groupMembers, 0) } - }) - } + } + + if !preExistingGroup && !tc.expectGroupExists { + // Verify that no prebuilds group exists + require.Error(t, err) + require.True(t, errors.Is(err, sql.ErrNoRows)) + } + }) } } } diff --git a/enterprise/coderd/prebuilds/reconcile.go b/enterprise/coderd/prebuilds/reconcile.go index 9af8fbc4cd9d5..f280436ea98c8 100644 --- a/enterprise/coderd/prebuilds/reconcile.go +++ b/enterprise/coderd/prebuilds/reconcile.go @@ -322,6 +322,12 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) (stats prebuilds.Rec return nil } + membershipReconciler := NewStoreMembershipReconciler(c.store, c.clock, logger) + err = membershipReconciler.ReconcileAll(ctx, database.PrebuildsSystemUserID, PrebuiltWorkspacesGroupName) + if err != nil { + return xerrors.Errorf("reconcile prebuild membership: %w", err) + } + snapshot, err := c.SnapshotState(ctx, c.store) if err != nil { return xerrors.Errorf("determine current snapshot: %w", err) @@ -334,12 +340,6 @@ func (c *StoreReconciler) ReconcileAll(ctx context.Context) (stats prebuilds.Rec return nil } - membershipReconciler := NewStoreMembershipReconciler(c.store, c.clock) - err = membershipReconciler.ReconcileAll(ctx, database.PrebuildsSystemUserID, snapshot.Presets) - if err != nil { - return xerrors.Errorf("reconcile prebuild membership: %w", err) - } - var eg errgroup.Group // Reconcile presets in parallel. Each preset in its own goroutine. for _, preset := range snapshot.Presets { From 566146af72a048adcff88a6390205df9181686bf Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 29 Oct 2025 15:31:41 +0000 Subject: [PATCH 012/255] fix(coderd): fix audit log resource link for tasks (#20545) Existing task audit log links were incorrect. As audit log links are generated on-the-fly, this does not require backfill. --- coderd/audit.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/audit.go b/coderd/audit.go index e43ed1c5128ec..3a3237a9fed50 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -509,11 +509,11 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit if err != nil { return "" } - workspace, err := api.Database.GetWorkspaceByID(ctx, task.WorkspaceID.UUID) + user, err := api.Database.GetUserByID(ctx, task.OwnerID) if err != nil { return "" } - return fmt.Sprintf("/tasks/%s/%s", workspace.OwnerName, task.Name) + return fmt.Sprintf("/tasks/%s/%s", user.Username, task.ID) default: return "" From 06dbadab11760fe5fbf88c5bfcac2c48e11f7862 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 29 Oct 2025 15:44:35 +0000 Subject: [PATCH 013/255] fix(coderd): ensure lifecycle executor has sufficient task permissions (#20539) We recently made a change to the `wsbuilder` to handle task related logic. Our test coverage for the lifecycle executor didn't handle this scenario and so we missed that it had insufficient permissions. This PR adds `Update` and `Read` permissions for `Task`s in the lifecycle executor, as well as an autostart/autostop test tailored to task workspaces to verify the change. --- Anthropic's Claude Sonnet 4.5 Thinking was involved in writing the tests --- coderd/autobuild/lifecycle_executor_test.go | 172 ++++++++++++++++++++ coderd/database/dbauthz/dbauthz.go | 1 + 2 files changed, 173 insertions(+) diff --git a/coderd/autobuild/lifecycle_executor_test.go b/coderd/autobuild/lifecycle_executor_test.go index 263a9e7e13c77..466c8c40525e1 100644 --- a/coderd/autobuild/lifecycle_executor_test.go +++ b/coderd/autobuild/lifecycle_executor_test.go @@ -1764,3 +1764,175 @@ func TestExecutorAutostartSkipsWhenNoProvisionersAvailable(t *testing.T) { assert.Len(t, stats.Transitions, 1, "should create builds when provisioners are available") } + +func TestExecutorTaskWorkspace(t *testing.T) { + t.Parallel() + + createTaskTemplate := func(t *testing.T, client *codersdk.Client, orgID uuid.UUID, ctx context.Context, defaultTTL time.Duration) codersdk.Template { + t.Helper() + + taskAppID := uuid.New() + version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: []*proto.Response{ + { + Type: &proto.Response_Plan{ + Plan: &proto.PlanComplete{HasAiTasks: true}, + }, + }, + }, + ProvisionApply: []*proto.Response{ + { + Type: &proto.Response_Apply{ + Apply: &proto.ApplyComplete{ + Resources: []*proto.Resource{ + { + Agents: []*proto.Agent{ + { + Id: uuid.NewString(), + Name: "dev", + Auth: &proto.Agent_Token{ + Token: uuid.NewString(), + }, + Apps: []*proto.App{ + { + Id: taskAppID.String(), + Slug: "task-app", + }, + }, + }, + }, + }, + }, + AiTasks: []*proto.AITask{ + { + AppId: taskAppID.String(), + }, + }, + }, + }, + }, + }, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, orgID, version.ID) + + if defaultTTL > 0 { + _, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ + DefaultTTLMillis: defaultTTL.Milliseconds(), + }) + require.NoError(t, err) + } + + return template + } + + createTaskWorkspace := func(t *testing.T, client *codersdk.Client, template codersdk.Template, ctx context.Context, input string) codersdk.Workspace { + t.Helper() + + exp := codersdk.NewExperimentalClient(client) + task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{ + TemplateVersionID: template.ActiveVersionID, + Input: input, + }) + require.NoError(t, err) + require.True(t, task.WorkspaceID.Valid, "task should have a workspace") + + workspace, err := client.Workspace(ctx, task.WorkspaceID.UUID) + require.NoError(t, err) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + + return workspace + } + + t.Run("Autostart", func(t *testing.T) { + t.Parallel() + + var ( + ctx = testutil.Context(t, testutil.WaitShort) + sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *") + tickCh = make(chan time.Time) + statsCh = make(chan autobuild.Stats) + client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{ + AutobuildTicker: tickCh, + IncludeProvisionerDaemon: true, + AutobuildStats: statsCh, + }) + admin = coderdtest.CreateFirstUser(t, client) + ) + + // Given: A task workspace + template := createTaskTemplate(t, client, admin.OrganizationID, ctx, 0) + workspace := createTaskWorkspace(t, client, template, ctx, "test task for autostart") + + // Given: The task workspace has an autostart schedule + err := client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{ + Schedule: ptr.Ref(sched.String()), + }) + require.NoError(t, err) + + // Given: That the workspace is in a stopped state. + workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop) + + p, err := coderdtest.GetProvisionerForTags(db, time.Now(), workspace.OrganizationID, map[string]string{}) + require.NoError(t, err) + + // When: the autobuild executor ticks after the scheduled time + go func() { + tickTime := sched.Next(workspace.LatestBuild.CreatedAt) + coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) + tickCh <- tickTime + close(tickCh) + }() + + // Then: We expect to see a start transition + stats := <-statsCh + require.Len(t, stats.Transitions, 1, "lifecycle executor should transition the task workspace") + assert.Contains(t, stats.Transitions, workspace.ID, "task workspace should be in transitions") + assert.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[workspace.ID], "should autostart the workspace") + require.Empty(t, stats.Errors, "should have no errors when managing task workspaces") + }) + + t.Run("Autostop", func(t *testing.T) { + t.Parallel() + + var ( + ctx = testutil.Context(t, testutil.WaitShort) + tickCh = make(chan time.Time) + statsCh = make(chan autobuild.Stats) + client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{ + AutobuildTicker: tickCh, + IncludeProvisionerDaemon: true, + AutobuildStats: statsCh, + }) + admin = coderdtest.CreateFirstUser(t, client) + ) + + // Given: A task workspace with an 8 hour deadline + template := createTaskTemplate(t, client, admin.OrganizationID, ctx, 8*time.Hour) + workspace := createTaskWorkspace(t, client, template, ctx, "test task for autostop") + + // Given: The workspace is currently running + workspace = coderdtest.MustWorkspace(t, client, workspace.ID) + require.Equal(t, codersdk.WorkspaceTransitionStart, workspace.LatestBuild.Transition) + require.NotZero(t, workspace.LatestBuild.Deadline, "workspace should have a deadline for autostop") + + p, err := coderdtest.GetProvisionerForTags(db, time.Now(), workspace.OrganizationID, map[string]string{}) + require.NoError(t, err) + + // When: the autobuild executor ticks after the deadline + go func() { + tickTime := workspace.LatestBuild.Deadline.Time.Add(time.Minute) + coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) + tickCh <- tickTime + close(tickCh) + }() + + // Then: We expect to see a stop transition + stats := <-statsCh + require.Len(t, stats.Transitions, 1, "lifecycle executor should transition the task workspace") + assert.Contains(t, stats.Transitions, workspace.ID, "task workspace should be in transitions") + assert.Equal(t, database.WorkspaceTransitionStop, stats.Transitions[workspace.ID], "should autostop the workspace") + require.Empty(t, stats.Errors, "should have no errors when managing task workspaces") + }) +} diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 95937a77d2075..1b2a6a5d97590 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -254,6 +254,7 @@ var ( rbac.ResourceFile.Type: {policy.ActionRead}, // Required to read terraform files rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead}, rbac.ResourceSystem.Type: {policy.WildcardSymbol}, + rbac.ResourceTask.Type: {policy.ActionRead, policy.ActionUpdate}, rbac.ResourceTemplate.Type: {policy.ActionRead, policy.ActionUpdate}, rbac.ResourceUser.Type: {policy.ActionRead}, rbac.ResourceWorkspace.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop}, From 1ebc2176248c4a77e1a3743938a3b9085a4e750e Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 29 Oct 2025 15:45:45 +0000 Subject: [PATCH 014/255] fix: update task link AppStatus using task_id (#20543) Fixes https://github.com/coder/coder/issues/20515 Alternative to https://github.com/coder/coder/pull/20519 Adds `task_id` to `workspaces_expanded` view and updates the "View Task" link in `AppStatuses` component. NOTE: this contains a migration --- cli/testdata/coder_list_--output_json.golden | 3 +- coderd/aitasks_test.go | 12 ++++++ coderd/apidoc/docs.go | 8 ++++ coderd/apidoc/swagger.json | 8 ++++ coderd/database/dump.sql | 8 ++-- ...00393_workspaces_expanded_task_id.down.sql | 39 +++++++++++++++++ .../000393_workspaces_expanded_task_id.up.sql | 42 +++++++++++++++++++ coderd/database/modelqueries.go | 1 + coderd/database/models.go | 1 + coderd/database/queries.sql.go | 26 ++++++++---- coderd/database/queries/workspaces.sql | 1 + coderd/workspaces.go | 1 + codersdk/workspaces.go | 2 + docs/reference/api/schemas.md | 9 ++++ docs/reference/api/workspaces.md | 24 +++++++++++ site/src/api/typesGenerated.ts | 4 ++ .../WorkspacePage/AppStatuses.stories.tsx | 21 +++++++++- site/src/pages/WorkspacePage/AppStatuses.tsx | 16 ++++--- .../pages/WorkspacePage/Workspace.stories.tsx | 4 +- site/src/testHelpers/entities.ts | 5 +++ 20 files changed, 213 insertions(+), 22 deletions(-) create mode 100644 coderd/database/migrations/000393_workspaces_expanded_task_id.down.sql create mode 100644 coderd/database/migrations/000393_workspaces_expanded_task_id.up.sql diff --git a/cli/testdata/coder_list_--output_json.golden b/cli/testdata/coder_list_--output_json.golden index 66afcf563dfbd..8da57536338f8 100644 --- a/cli/testdata/coder_list_--output_json.golden +++ b/cli/testdata/coder_list_--output_json.golden @@ -90,6 +90,7 @@ "allow_renames": false, "favorite": false, "next_start_at": "====[timestamp]=====", - "is_prebuild": false + "is_prebuild": false, + "task_id": null } ] diff --git a/coderd/aitasks_test.go b/coderd/aitasks_test.go index 80af3e993e97a..d3b5e240d8301 100644 --- a/coderd/aitasks_test.go +++ b/coderd/aitasks_test.go @@ -259,6 +259,9 @@ func TestTasks(t *testing.T) { // Wait for the workspace to be built. workspace, err := client.Workspace(ctx, task.WorkspaceID.UUID) require.NoError(t, err) + if assert.True(t, workspace.TaskID.Valid, "task id should be set on workspace") { + assert.Equal(t, task.ID, workspace.TaskID.UUID, "workspace task id should match") + } coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) // List tasks via experimental API and verify the prompt and status mapping. @@ -297,6 +300,9 @@ func TestTasks(t *testing.T) { // Get the workspace and wait for it to be ready. ws, err := client.Workspace(ctx, task.WorkspaceID.UUID) require.NoError(t, err) + if assert.True(t, ws.TaskID.Valid, "task id should be set on workspace") { + assert.Equal(t, task.ID, ws.TaskID.UUID, "workspace task id should match") + } coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) ws = coderdtest.MustWorkspace(t, client, task.WorkspaceID.UUID) // Assert invariant: the workspace has exactly one resource with one agent with one app. @@ -371,6 +377,9 @@ func TestTasks(t *testing.T) { require.True(t, task.WorkspaceID.Valid, "task should have a workspace ID") ws, err := client.Workspace(ctx, task.WorkspaceID.UUID) require.NoError(t, err) + if assert.True(t, ws.TaskID.Valid, "task id should be set on workspace") { + assert.Equal(t, task.ID, ws.TaskID.UUID, "workspace task id should match") + } coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) err = exp.DeleteTask(ctx, "me", task.ID) @@ -417,6 +426,9 @@ func TestTasks(t *testing.T) { coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) ws := coderdtest.CreateWorkspace(t, client, template.ID) + if assert.False(t, ws.TaskID.Valid, "task id should not be set on non-task workspace") { + assert.Zero(t, ws.TaskID, "non-task workspace task id should be empty") + } coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) exp := codersdk.NewExperimentalClient(client) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 344a0299f8405..e459b94b3fdaf 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -19712,6 +19712,14 @@ const docTemplate = `{ "description": "OwnerName is the username of the owner of the workspace.", "type": "string" }, + "task_id": { + "description": "TaskID, if set, indicates that the workspace is relevant to the given codersdk.Task.", + "allOf": [ + { + "$ref": "#/definitions/uuid.NullUUID" + } + ] + }, "template_active_version_id": { "type": "string", "format": "uuid" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index b472f2f4ef53f..8eef9dfb8e0d7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -18098,6 +18098,14 @@ "description": "OwnerName is the username of the owner of the workspace.", "type": "string" }, + "task_id": { + "description": "TaskID, if set, indicates that the workspace is relevant to the given codersdk.Task.", + "allOf": [ + { + "$ref": "#/definitions/uuid.NullUUID" + } + ] + }, "template_active_version_id": { "type": "string", "format": "uuid" diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 837c657402bf2..8790bd27df693 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -2922,11 +2922,13 @@ CREATE VIEW workspaces_expanded AS templates.name AS template_name, templates.display_name AS template_display_name, templates.icon AS template_icon, - templates.description AS template_description - FROM (((workspaces + templates.description AS template_description, + tasks.id AS task_id + FROM ((((workspaces JOIN visible_users ON ((workspaces.owner_id = visible_users.id))) JOIN organizations ON ((workspaces.organization_id = organizations.id))) - JOIN templates ON ((workspaces.template_id = templates.id))); + JOIN templates ON ((workspaces.template_id = templates.id))) + LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id))); COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/migrations/000393_workspaces_expanded_task_id.down.sql b/coderd/database/migrations/000393_workspaces_expanded_task_id.down.sql new file mode 100644 index 0000000000000..ed30e6a0f64f3 --- /dev/null +++ b/coderd/database/migrations/000393_workspaces_expanded_task_id.down.sql @@ -0,0 +1,39 @@ +DROP VIEW workspaces_expanded; + +-- Recreate the view from 000354_workspace_acl.up.sql +CREATE VIEW workspaces_expanded AS + SELECT workspaces.id, + workspaces.created_at, + workspaces.updated_at, + workspaces.owner_id, + workspaces.organization_id, + workspaces.template_id, + workspaces.deleted, + workspaces.name, + workspaces.autostart_schedule, + workspaces.ttl, + workspaces.last_used_at, + workspaces.dormant_at, + workspaces.deleting_at, + workspaces.automatic_updates, + workspaces.favorite, + workspaces.next_start_at, + workspaces.group_acl, + workspaces.user_acl, + visible_users.avatar_url AS owner_avatar_url, + visible_users.username AS owner_username, + visible_users.name AS owner_name, + organizations.name AS organization_name, + organizations.display_name AS organization_display_name, + organizations.icon AS organization_icon, + organizations.description AS organization_description, + templates.name AS template_name, + templates.display_name AS template_display_name, + templates.icon AS template_icon, + templates.description AS template_description + FROM (((workspaces + JOIN visible_users ON ((workspaces.owner_id = visible_users.id))) + JOIN organizations ON ((workspaces.organization_id = organizations.id))) + JOIN templates ON ((workspaces.template_id = templates.id))); + +COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/migrations/000393_workspaces_expanded_task_id.up.sql b/coderd/database/migrations/000393_workspaces_expanded_task_id.up.sql new file mode 100644 index 0000000000000..f01354e65bd50 --- /dev/null +++ b/coderd/database/migrations/000393_workspaces_expanded_task_id.up.sql @@ -0,0 +1,42 @@ +DROP VIEW workspaces_expanded; + +-- Add nullable task_id to workspaces_expanded view +CREATE VIEW workspaces_expanded AS + SELECT workspaces.id, + workspaces.created_at, + workspaces.updated_at, + workspaces.owner_id, + workspaces.organization_id, + workspaces.template_id, + workspaces.deleted, + workspaces.name, + workspaces.autostart_schedule, + workspaces.ttl, + workspaces.last_used_at, + workspaces.dormant_at, + workspaces.deleting_at, + workspaces.automatic_updates, + workspaces.favorite, + workspaces.next_start_at, + workspaces.group_acl, + workspaces.user_acl, + visible_users.avatar_url AS owner_avatar_url, + visible_users.username AS owner_username, + visible_users.name AS owner_name, + organizations.name AS organization_name, + organizations.display_name AS organization_display_name, + organizations.icon AS organization_icon, + organizations.description AS organization_description, + templates.name AS template_name, + templates.display_name AS template_display_name, + templates.icon AS template_icon, + templates.description AS template_description, + tasks.id AS task_id + FROM ((((workspaces + JOIN visible_users ON ((workspaces.owner_id = visible_users.id))) + JOIN organizations ON ((workspaces.organization_id = organizations.id))) + JOIN templates ON ((workspaces.template_id = templates.id))) + LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id))); + +COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.'; + diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index c9c7879627684..f9b058a40986e 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -321,6 +321,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, &i.TemplateVersionID, &i.TemplateVersionName, &i.LatestBuildCompletedAt, diff --git a/coderd/database/models.go b/coderd/database/models.go index e55f3a553721b..ade3348ba3c69 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -4663,6 +4663,7 @@ type Workspace struct { TemplateDisplayName string `db:"template_display_name" json:"template_display_name"` TemplateIcon string `db:"template_icon" json:"template_icon"` TemplateDescription string `db:"template_description" json:"template_description"` + TaskID uuid.NullUUID `db:"task_id" json:"task_id"` } type WorkspaceAgent struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index de5a4d02338bc..ff32a1126792d 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -21927,7 +21927,7 @@ func (q *sqlQuerier) GetWorkspaceACLByID(ctx context.Context, id uuid.UUID) (Get const getWorkspaceByAgentID = `-- name: GetWorkspaceByAgentID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded as workspaces WHERE @@ -21988,13 +21988,14 @@ func (q *sqlQuerier) GetWorkspaceByAgentID(ctx context.Context, agentID uuid.UUI &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } const getWorkspaceByID = `-- name: GetWorkspaceByID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded WHERE @@ -22036,13 +22037,14 @@ func (q *sqlQuerier) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Worksp &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } const getWorkspaceByOwnerIDAndName = `-- name: GetWorkspaceByOwnerIDAndName :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded as workspaces WHERE @@ -22091,13 +22093,14 @@ func (q *sqlQuerier) GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWo &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } const getWorkspaceByResourceID = `-- name: GetWorkspaceByResourceID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded as workspaces WHERE @@ -22153,13 +22156,14 @@ func (q *sqlQuerier) GetWorkspaceByResourceID(ctx context.Context, resourceID uu &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } const getWorkspaceByWorkspaceAppID = `-- name: GetWorkspaceByWorkspaceAppID :one SELECT - id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description + id, created_at, updated_at, owner_id, organization_id, template_id, deleted, name, autostart_schedule, ttl, last_used_at, dormant_at, deleting_at, automatic_updates, favorite, next_start_at, group_acl, user_acl, owner_avatar_url, owner_username, owner_name, organization_name, organization_display_name, organization_icon, organization_description, template_name, template_display_name, template_icon, template_description, task_id FROM workspaces_expanded as workspaces WHERE @@ -22227,6 +22231,7 @@ func (q *sqlQuerier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspace &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, ) return i, err } @@ -22276,7 +22281,7 @@ SELECT ), filtered_workspaces AS ( SELECT - workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description, + workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspaces.owner_avatar_url, workspaces.owner_username, workspaces.owner_name, workspaces.organization_name, workspaces.organization_display_name, workspaces.organization_icon, workspaces.organization_description, workspaces.template_name, workspaces.template_display_name, workspaces.template_icon, workspaces.template_description, workspaces.task_id, latest_build.template_version_id, latest_build.template_version_name, latest_build.completed_at as latest_build_completed_at, @@ -22567,7 +22572,7 @@ WHERE -- @authorize_filter ), filtered_workspaces_order AS ( SELECT - fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_ai_task, fw.latest_build_has_external_agent + fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.task_id, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_ai_task, fw.latest_build_has_external_agent FROM filtered_workspaces fw ORDER BY @@ -22588,7 +22593,7 @@ WHERE $25 ), filtered_workspaces_order_with_summary AS ( SELECT - fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_ai_task, fwo.latest_build_has_external_agent + fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.task_id, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_ai_task, fwo.latest_build_has_external_agent FROM filtered_workspaces_order fwo -- Return a technical summary row with total count of workspaces. @@ -22624,6 +22629,7 @@ WHERE '', -- template_display_name '', -- template_icon '', -- template_description + '00000000-0000-0000-0000-000000000000'::uuid, -- task_id -- Extra columns added to ` + "`" + `filtered_workspaces` + "`" + ` '00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id '', -- template_version_name @@ -22643,7 +22649,7 @@ WHERE filtered_workspaces ) SELECT - fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_ai_task, fwos.latest_build_has_external_agent, + fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.task_id, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_ai_task, fwos.latest_build_has_external_agent, tc.count FROM filtered_workspaces_order_with_summary fwos @@ -22711,6 +22717,7 @@ type GetWorkspacesRow struct { TemplateDisplayName string `db:"template_display_name" json:"template_display_name"` TemplateIcon string `db:"template_icon" json:"template_icon"` TemplateDescription string `db:"template_description" json:"template_description"` + TaskID uuid.NullUUID `db:"task_id" json:"task_id"` TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"` LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"` @@ -22793,6 +22800,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) &i.TemplateDisplayName, &i.TemplateIcon, &i.TemplateDescription, + &i.TaskID, &i.TemplateVersionID, &i.TemplateVersionName, &i.LatestBuildCompletedAt, diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 8ccc69b9a813c..d48285bb7de9c 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -457,6 +457,7 @@ WHERE '', -- template_display_name '', -- template_icon '', -- template_description + '00000000-0000-0000-0000-000000000000'::uuid, -- task_id -- Extra columns added to `filtered_workspaces` '00000000-0000-0000-0000-000000000000'::uuid, -- template_version_id '', -- template_version_name diff --git a/coderd/workspaces.go b/coderd/workspaces.go index e8b7ff51530c3..3519442c3e6bf 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -2654,6 +2654,7 @@ func convertWorkspace( Favorite: requesterFavorite, NextStartAt: nextStartAt, IsPrebuild: workspace.IsPrebuild(), + TaskID: workspace.TaskID, }, nil } diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index f190d58be6bfb..709c9257c8350 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -72,6 +72,8 @@ type Workspace struct { // Once a prebuilt workspace is claimed by a user, it transitions to a regular workspace, // and IsPrebuild returns false. IsPrebuild bool `json:"is_prebuild"` + // TaskID, if set, indicates that the workspace is relevant to the given codersdk.Task. + TaskID uuid.NullUUID `json:"task_id,omitempty"` } func (w Workspace) FullName() string { diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 037c9cfa109bb..4317324f002be 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -10184,6 +10184,10 @@ If the schedule is empty, the user will be updated to use the default schedule.| "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -10222,6 +10226,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `owner_avatar_url` | string | false | | | | `owner_id` | string | false | | | | `owner_name` | string | false | | Owner name is the username of the owner of the workspace. | +| `task_id` | [uuid.NullUUID](#uuidnulluuid) | false | | Task ID if set, indicates that the workspace is relevant to the given codersdk.Task. | | `template_active_version_id` | string | false | | | | `template_allow_user_cancel_workspace_jobs` | boolean | false | | | | `template_display_name` | string | false | | | @@ -12178,6 +12183,10 @@ If the schedule is empty, the user will be updated to use the default schedule.| "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", diff --git a/docs/reference/api/workspaces.md b/docs/reference/api/workspaces.md index 91ab23f9260e9..3e52d9e0a2d60 100644 --- a/docs/reference/api/workspaces.md +++ b/docs/reference/api/workspaces.md @@ -297,6 +297,10 @@ of the template will be used. "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -589,6 +593,10 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -906,6 +914,10 @@ of the template will be used. "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -1184,6 +1196,10 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -1477,6 +1493,10 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", @@ -2029,6 +2049,10 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \ "owner_avatar_url": "string", "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", "owner_name": "string", + "task_id": { + "uuid": "string", + "valid": true + }, "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", "template_allow_user_cancel_workspace_jobs": true, "template_display_name": "string", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index b964cf4d05f28..6d703cbcfe2d6 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -5875,6 +5875,10 @@ export interface Workspace { * and IsPrebuild returns false. */ readonly is_prebuild: boolean; + /** + * TaskID, if set, indicates that the workspace is relevant to the given codersdk.Task. + */ + readonly task_id?: string; } // From codersdk/workspaces.go diff --git a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx index 2e8324aef2e0b..d27e64ae7096f 100644 --- a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx +++ b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx @@ -1,5 +1,6 @@ import { createTimestamp, + MockTaskWorkspace, MockWorkspace, MockWorkspaceAgent, MockWorkspaceApp, @@ -18,7 +19,7 @@ const meta: Meta = { args: { referenceDate: new Date("2024-03-26T15:15:00Z"), agent: mockAgent(MockWorkspaceAppStatuses), - workspace: MockWorkspace, + workspace: MockTaskWorkspace, }, decorators: [withProxyProvider()], }; @@ -148,6 +149,24 @@ export const MultipleStatuses: Story = { }, }; +export const NoTaskWorkspace: Story = { + args: { + agent: mockAgent([ + { + ...MockWorkspaceAppStatus, + id: "status-9", + icon: "", + message: "status updated via curl", + created_at: createTimestamp(5, 15), + uri: "", + state: "complete" as const, + }, + ...MockWorkspaceAppStatuses, + ]), + workspace: MockWorkspace, + }, +}; + function mockAgent(statuses: WorkspaceAppStatus[]) { return { ...MockWorkspaceAgent, diff --git a/site/src/pages/WorkspacePage/AppStatuses.tsx b/site/src/pages/WorkspacePage/AppStatuses.tsx index 26f239b627101..2f1845db37031 100644 --- a/site/src/pages/WorkspacePage/AppStatuses.tsx +++ b/site/src/pages/WorkspacePage/AppStatuses.tsx @@ -121,12 +121,16 @@ export const AppStatuses: FC = ({ ))} - + {workspace.task_id && ( + + )} diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx index 5a49e0fa57091..8ae0c1b3095d0 100644 --- a/site/src/pages/WorkspacePage/Workspace.stories.tsx +++ b/site/src/pages/WorkspacePage/Workspace.stories.tsx @@ -112,9 +112,9 @@ export const RunningWithChildAgent: Story = { export const RunningWithAppStatuses: Story = { args: { workspace: { - ...Mocks.MockWorkspace, + ...Mocks.MockTaskWorkspace, latest_build: { - ...Mocks.MockWorkspace.latest_build, + ...Mocks.MockTaskWorkspace.latest_build, resources: [ { ...Mocks.MockWorkspaceResource, diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index a12d91cf51a8e..b0ce392fa1feb 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -5027,6 +5027,11 @@ export const MockTask = { updated_at: "2022-05-17T17:39:01.382927298Z", } satisfies TypesGen.Task; +export const MockTaskWorkspace: TypesGen.Workspace = { + ...MockWorkspace, + task_id: MockTask.id, +}; + export const MockTasks = [ MockTask, { From 303e9ef7def51f275061f7230c75d0e491654386 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 29 Oct 2025 18:45:56 +0200 Subject: [PATCH 015/255] fix: switch to coder/sqlc fork (#20536) Refs https://github.com/coder/sqlc/pull/1 Unblocks https://github.com/coder/coder/pull/20501 Upstream https://github.com/sqlc-dev/sqlc/pull/4159 --- .github/actions/setup-sqlc/action.yaml | 13 +++++++++--- coderd/database/models.go | 2 +- coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 2 +- dogfood/coder/Dockerfile | 7 ++++++- flake.nix | 28 +++++++++++++++++++++++++- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/.github/actions/setup-sqlc/action.yaml b/.github/actions/setup-sqlc/action.yaml index c123cb8cc3156..8e1cf8c50f4db 100644 --- a/.github/actions/setup-sqlc/action.yaml +++ b/.github/actions/setup-sqlc/action.yaml @@ -5,6 +5,13 @@ runs: using: "composite" steps: - name: Setup sqlc - uses: sqlc-dev/setup-sqlc@c0209b9199cd1cce6a14fc27cabcec491b651761 # v4.0.0 - with: - sqlc-version: "1.27.0" + # uses: sqlc-dev/setup-sqlc@c0209b9199cd1cce6a14fc27cabcec491b651761 # v4.0.0 + # with: + # sqlc-version: "1.30.0" + + # Switched to coder/sqlc fork to fix ambiguous column bug, see: + # - https://github.com/coder/sqlc/pull/1 + # - https://github.com/sqlc-dev/sqlc/pull/4159 + shell: bash + run: | + CGO_ENABLED=1 go install github.com/coder/sqlc/cmd/sqlc@aab4e865a51df0c43e1839f81a9d349b41d14f05 diff --git a/coderd/database/models.go b/coderd/database/models.go index ade3348ba3c69..0c132a33d0095 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 package database diff --git a/coderd/database/querier.go b/coderd/database/querier.go index b1a450939834d..2739cb7430d9f 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 package database diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ff32a1126792d..eafe73279b158 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 package database diff --git a/dogfood/coder/Dockerfile b/dogfood/coder/Dockerfile index a8fe252554c8f..df668f7c8fb6e 100644 --- a/dogfood/coder/Dockerfile +++ b/dogfood/coder/Dockerfile @@ -62,7 +62,12 @@ RUN apt-get update && \ # charts and values files go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.5.0 && \ # sqlc for Go code generation - (CGO_ENABLED=1 go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.27.0) && \ + # (CGO_ENABLED=1 go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.27.0) && \ + # + # Switched to coder/sqlc fork to fix ambiguous column bug, see: + # - https://github.com/coder/sqlc/pull/1 + # - https://github.com/sqlc-dev/sqlc/pull/4159 + (CGO_ENABLED=1 go install github.com/coder/sqlc/cmd/sqlc@aab4e865a51df0c43e1839f81a9d349b41d14f05) && \ # gcr-cleaner-cli used by CI to prune unused images go install github.com/sethvargo/gcr-cleaner/cmd/gcr-cleaner-cli@v0.5.1 && \ # ruleguard for checking custom rules, without needing to run all of diff --git a/flake.nix b/flake.nix index c76c5bbba61ba..38eb53b68faee 100644 --- a/flake.nix +++ b/flake.nix @@ -84,6 +84,31 @@ vendorHash = null; }; + # Custom sqlc build from coder/sqlc fork to fix ambiguous column bug, see: + # - https://github.com/coder/sqlc/pull/1 + # - https://github.com/sqlc-dev/sqlc/pull/4159 + # + # To update hashes: + # 1. Run: `nix --extra-experimental-features 'nix-command flakes' build .#devShells.x86_64-linux.default` + # 2. Nix will fail with the correct sha256 hash for src + # 3. Update the sha256 and run again + # 4. Nix will fail with the correct vendorHash + # 5. Update the vendorHash + sqlc-custom = unstablePkgs.buildGo124Module { + pname = "sqlc"; + version = "coder-fork-aab4e865a51df0c43e1839f81a9d349b41d14f05"; + + src = pkgs.fetchFromGitHub { + owner = "coder"; + repo = "sqlc"; + rev = "aab4e865a51df0c43e1839f81a9d349b41d14f05"; + sha256 = "sha256-zXjTypEFWDOkoZMKHMMRtAz2coNHSCkQ+nuZ8rOnzZ8="; + }; + + subPackages = [ "cmd/sqlc" ]; + vendorHash = "sha256-69kg3qkvEWyCAzjaCSr3a73MNonub9sZTYyGaCW+UTI="; + }; + # Packages required to build the frontend frontendPackages = with pkgs; @@ -163,7 +188,8 @@ ripgrep shellcheck (pinnedPkgs.shfmt) - sqlc + # sqlc + sqlc-custom syft unstablePkgs.terraform typos From 92b63871ca2c35f62d253d778142f23dadde8b75 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Thu, 30 Oct 2025 04:03:00 +1100 Subject: [PATCH 016/255] chore: remove dogfood envbuilder template deployment (#20546) Already missing in state, verified with `terraform state list` --- dogfood/main.tf | 93 +++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/dogfood/main.tf b/dogfood/main.tf index c79e950efadf4..6fcfa90739eae 100644 --- a/dogfood/main.tf +++ b/dogfood/main.tf @@ -87,49 +87,50 @@ resource "coderd_template" "dogfood" { time_til_dormant_ms = 8640000000 } - -resource "coderd_template" "envbuilder_dogfood" { - name = "coder-envbuilder" - display_name = "Write Coder on Coder using Envbuilder" - description = "Write Coder on Coder using a workspace built by Envbuilder." - icon = "/emojis/1f3d7.png" # 🏗️ - organization_id = data.coderd_organization.default.id - versions = [ - { - name = var.CODER_TEMPLATE_VERSION - message = var.CODER_TEMPLATE_MESSAGE - directory = "./coder-envbuilder" - active = true - tf_vars = [{ - # clusters/dogfood-v2/coder/provisioner/configs/values.yaml#L191-L194 - name = "envbuilder_cache_dockerconfigjson_path" - value = "/home/coder/envbuilder-cache-dockerconfig.json" - }] - } - ] - acl = { - groups = [{ - id = data.coderd_organization.default.id - role = "use" - }] - users = [{ - id = data.coderd_user.machine.id - role = "admin" - }] - } - activity_bump_ms = 10800000 - allow_user_auto_start = true - allow_user_auto_stop = true - allow_user_cancel_workspace_jobs = false - auto_start_permitted_days_of_week = ["friday", "monday", "saturday", "sunday", "thursday", "tuesday", "wednesday"] - auto_stop_requirement = { - days_of_week = ["sunday"] - weeks = 1 - } - default_ttl_ms = 28800000 - deprecation_message = null - failure_ttl_ms = 604800000 - require_active_version = true - time_til_dormant_autodelete_ms = 7776000000 - time_til_dormant_ms = 8640000000 -} +# Disabled for now until we restore the deleted template. +# My bad - Dean +#resource "coderd_template" "envbuilder_dogfood" { +# name = "coder-envbuilder" +# display_name = "Write Coder on Coder using Envbuilder" +# description = "Write Coder on Coder using a workspace built by Envbuilder." +# icon = "/emojis/1f3d7.png" # 🏗️ +# organization_id = data.coderd_organization.default.id +# versions = [ +# { +# name = var.CODER_TEMPLATE_VERSION +# message = var.CODER_TEMPLATE_MESSAGE +# directory = "./coder-envbuilder" +# active = true +# tf_vars = [{ +# # clusters/dogfood-v2/coder/provisioner/configs/values.yaml#L191-L194 +# name = "envbuilder_cache_dockerconfigjson_path" +# value = "/home/coder/envbuilder-cache-dockerconfig.json" +# }] +# } +# ] +# acl = { +# groups = [{ +# id = data.coderd_organization.default.id +# role = "use" +# }] +# users = [{ +# id = data.coderd_user.machine.id +# role = "admin" +# }] +# } +# activity_bump_ms = 10800000 +# allow_user_auto_start = true +# allow_user_auto_stop = true +# allow_user_cancel_workspace_jobs = false +# auto_start_permitted_days_of_week = ["friday", "monday", "saturday", "sunday", "thursday", "tuesday", "wednesday"] +# auto_stop_requirement = { +# days_of_week = ["sunday"] +# weeks = 1 +# } +# default_ttl_ms = 28800000 +# deprecation_message = null +# failure_ttl_ms = 604800000 +# require_active_version = true +# time_til_dormant_autodelete_ms = 7776000000 +# time_til_dormant_ms = 8640000000 +#} From 9986dc0c38e418083d54ae7a9c828aed7440fc07 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Thu, 30 Oct 2025 04:03:14 +1100 Subject: [PATCH 017/255] chore: remove brazil region from dogfood (#20548) --- dogfood/coder-envbuilder/main.tf | 8 +------- dogfood/coder/main.tf | 24 ------------------------ 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/dogfood/coder-envbuilder/main.tf b/dogfood/coder-envbuilder/main.tf index cd316100fea8e..c334a9642e7ce 100644 --- a/dogfood/coder-envbuilder/main.tf +++ b/dogfood/coder-envbuilder/main.tf @@ -24,7 +24,6 @@ locals { // actually in Germany now. "eu-helsinki" = "tcp://katerose-fsn-cdr-dev.tailscale.svc.cluster.local:2375" "ap-sydney" = "tcp://wolfgang-syd-cdr-dev.tailscale.svc.cluster.local:2375" - "sa-saopaulo" = "tcp://oberstein-sao-cdr-dev.tailscale.svc.cluster.local:2375" "za-jnb" = "tcp://greenhill-jnb-cdr-dev.tailscale.svc.cluster.local:2375" } @@ -72,11 +71,6 @@ data "coder_parameter" "region" { name = "Sydney" value = "ap-sydney" } - option { - icon = "/emojis/1f1e7-1f1f7.png" - name = "São Paulo" - value = "sa-saopaulo" - } option { icon = "/emojis/1f1ff-1f1e6.png" name = "Johannesburg" @@ -446,4 +440,4 @@ resource "coder_metadata" "container_info" { key = "region" value = data.coder_parameter.region.option[index(data.coder_parameter.region.option.*.value, data.coder_parameter.region.value)].name } -} \ No newline at end of file +} diff --git a/dogfood/coder/main.tf b/dogfood/coder/main.tf index 37a53349bb903..4064544b6e6fa 100644 --- a/dogfood/coder/main.tf +++ b/dogfood/coder/main.tf @@ -31,7 +31,6 @@ locals { // actually in Germany now. "eu-helsinki" = "tcp://katerose-fsn-cdr-dev.tailscale.svc.cluster.local:2375" "ap-sydney" = "tcp://wolfgang-syd-cdr-dev.tailscale.svc.cluster.local:2375" - "sa-saopaulo" = "tcp://oberstein-sao-cdr-dev.tailscale.svc.cluster.local:2375" "za-cpt" = "tcp://schonkopf-cpt-cdr-dev.tailscale.svc.cluster.local:2375" } @@ -109,23 +108,6 @@ data "coder_workspace_preset" "sydney" { } } -data "coder_workspace_preset" "saopaulo" { - name = "São Paulo" - description = "Development workspace hosted in Brazil with 1 prebuild instance" - icon = "/emojis/1f1e7-1f1f7.png" - parameters = { - (data.coder_parameter.region.name) = "sa-saopaulo" - (data.coder_parameter.image_type.name) = "codercom/oss-dogfood:latest" - (data.coder_parameter.repo_base_dir.name) = "~" - (data.coder_parameter.res_mon_memory_threshold.name) = 80 - (data.coder_parameter.res_mon_volume_threshold.name) = 90 - (data.coder_parameter.res_mon_volume_path.name) = "/home/coder" - } - prebuilds { - instances = 1 - } -} - data "coder_parameter" "repo_base_dir" { type = "string" name = "Coder Repository Base Directory" @@ -157,7 +139,6 @@ locals { "north-america" : "us-pittsburgh" "europe" : "eu-helsinki" "australia" : "ap-sydney" - "south-america" : "sa-saopaulo" "africa" : "za-cpt" } @@ -190,11 +171,6 @@ data "coder_parameter" "region" { name = "Sydney" value = "ap-sydney" } - option { - icon = "/emojis/1f1e7-1f1f7.png" - name = "São Paulo" - value = "sa-saopaulo" - } option { icon = "/emojis/1f1ff-1f1e6.png" name = "Cape Town" From 50749d131b65b3ce04b02dc91b3d5dc9ed563c2e Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 29 Oct 2025 17:33:03 +0000 Subject: [PATCH 018/255] ci: workaround for get.helm.sh outage (#20552) --- .github/workflows/ci.yaml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 96d0ce23953cf..5efa2edbc9fb0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -204,9 +204,17 @@ jobs: # Needed for helm chart linting - name: Install helm - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 - with: - version: v3.9.2 + # uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 + # with: + # version: v3.9.2 + # The below is taken from https://helm.sh/docs/intro/install/#from-apt-debianubuntu + run: | + set -euo pipefail + sudo apt-get install curl gpg apt-transport-https --yes + curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null + echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list + sudo apt-get update + sudo apt-get install helm - name: make lint run: | From 859e94d67a8197c258c8d4fa45d54086558edfad Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 29 Oct 2025 20:59:12 +0200 Subject: [PATCH 019/255] fix: deprecate codersdk.AITaskPromptParameterName and reduce usage (#20501) Depends on coder/sqlc#1 Fixes coder/internal#979 Updates coder/internal#973 --- cli/exp_task_list_test.go | 78 +------- cli/exp_task_test.go | 5 +- coderd/aitasks.go | 77 +++----- coderd/aitasks_test.go | 174 +++++------------- coderd/coderd.go | 5 +- coderd/database/modelqueries.go | 1 - coderd/database/queries.sql.go | 40 ++-- coderd/database/queries/workspaces.sql | 32 ++-- coderd/workspaces_test.go | 106 ++++------- codersdk/aitasks.go | 46 +---- site/src/api/api.ts | 21 --- site/src/api/typesGenerated.ts | 22 +-- .../modules/tasks/TaskPrompt/TaskPrompt.tsx | 5 +- 13 files changed, 164 insertions(+), 448 deletions(-) diff --git a/cli/exp_task_list_test.go b/cli/exp_task_list_test.go index d297310dc4fc3..f9255da9b3849 100644 --- a/cli/exp_task_list_test.go +++ b/cli/exp_task_list_test.go @@ -2,7 +2,6 @@ package cli_test import ( "bytes" - "context" "database/sql" "encoding/json" "io" @@ -19,10 +18,7 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbfake" - "github.com/coder/coder/v2/coderd/database/dbgen" - "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/pty/ptytest" @@ -43,76 +39,22 @@ func makeAITask(t *testing.T, db database.Store, orgID, adminID, ownerID uuid.UU }, }).Do() - ws := database.WorkspaceTable{ + build := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ OrganizationID: orgID, OwnerID: ownerID, TemplateID: tv.Template.ID, - } - build := dbfake.WorkspaceBuild(t, db, ws). + }). Seed(database.WorkspaceBuild{ TemplateVersionID: tv.TemplateVersion.ID, Transition: transition, - }).WithAgent().Do() - dbgen.WorkspaceBuildParameters(t, db, []database.WorkspaceBuildParameter{ - { - WorkspaceBuildID: build.Build.ID, - Name: codersdk.AITaskPromptParameterName, - Value: prompt, - }, - }) - agents, err := db.GetWorkspaceAgentsByWorkspaceAndBuildNumber( - dbauthz.AsSystemRestricted(context.Background()), - database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams{ - WorkspaceID: build.Workspace.ID, - BuildNumber: build.Build.BuildNumber, - }, - ) - require.NoError(t, err) - require.NotEmpty(t, agents) - agentID := agents[0].ID - - // Create a workspace app and set it as the sidebar app. - app := dbgen.WorkspaceApp(t, db, database.WorkspaceApp{ - AgentID: agentID, - Slug: "task-sidebar", - DisplayName: "Task Sidebar", - External: false, - }) - - // Update build flags to reference the sidebar app and HasAITask=true. - err = db.UpdateWorkspaceBuildFlagsByID( - dbauthz.AsSystemRestricted(context.Background()), - database.UpdateWorkspaceBuildFlagsByIDParams{ - ID: build.Build.ID, - HasAITask: sql.NullBool{Bool: true, Valid: true}, - HasExternalAgent: sql.NullBool{Bool: false, Valid: false}, - SidebarAppID: uuid.NullUUID{UUID: app.ID, Valid: true}, - UpdatedAt: build.Build.UpdatedAt, - }, - ) - require.NoError(t, err) - - // Create a task record in the tasks table for the new data model. - task := dbgen.Task(t, db, database.TaskTable{ - OrganizationID: orgID, - OwnerID: ownerID, - Name: build.Workspace.Name, - WorkspaceID: uuid.NullUUID{UUID: build.Workspace.ID, Valid: true}, - TemplateVersionID: tv.TemplateVersion.ID, - TemplateParameters: []byte("{}"), - Prompt: prompt, - CreatedAt: dbtime.Now(), - }) - - // Link the task to the workspace app. - dbgen.TaskWorkspaceApp(t, db, database.TaskWorkspaceApp{ - TaskID: task.ID, - WorkspaceBuildNumber: build.Build.BuildNumber, - WorkspaceAgentID: uuid.NullUUID{UUID: agentID, Valid: true}, - WorkspaceAppID: uuid.NullUUID{UUID: app.ID, Valid: true}, - }) - - return task + }). + WithAgent(). + WithTask(database.TaskTable{ + Prompt: prompt, + }, nil). + Do() + + return build.Task } func TestExpTaskList(t *testing.T) { diff --git a/cli/exp_task_test.go b/cli/exp_task_test.go index d2d3728aeb280..71732902497b1 100644 --- a/cli/exp_task_test.go +++ b/cli/exp_task_test.go @@ -293,7 +293,6 @@ func createAITaskTemplate(t *testing.T, client *codersdk.Client, orgID uuid.UUID { Type: &proto.Response_Plan{ Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}}, HasAiTasks: true, }, }, @@ -328,9 +327,7 @@ func createAITaskTemplate(t *testing.T, client *codersdk.Client, orgID uuid.UUID }, AiTasks: []*proto.AITask{ { - SidebarApp: &proto.AITaskSidebarApp{ - Id: taskAppID.String(), - }, + AppId: taskAppID.String(), }, }, }, diff --git a/coderd/aitasks.go b/coderd/aitasks.go index 8415a01454d0a..9611785b853f5 100644 --- a/coderd/aitasks.go +++ b/coderd/aitasks.go @@ -7,7 +7,6 @@ import ( "net/http" "net/url" "slices" - "strings" "time" "github.com/google/uuid" @@ -24,62 +23,12 @@ import ( "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/searchquery" "github.com/coder/coder/v2/coderd/taskname" + "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/codersdk" aiagentapi "github.com/coder/agentapi-sdk-go" ) -// This endpoint is experimental and not guaranteed to be stable, so we're not -// generating public-facing documentation for it. -func (api *API) aiTasksPrompts(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - buildIDsParam := r.URL.Query().Get("build_ids") - if buildIDsParam == "" { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "build_ids query parameter is required", - }) - return - } - - // Parse build IDs - buildIDStrings := strings.Split(buildIDsParam, ",") - buildIDs := make([]uuid.UUID, 0, len(buildIDStrings)) - for _, idStr := range buildIDStrings { - id, err := uuid.Parse(strings.TrimSpace(idStr)) - if err != nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: fmt.Sprintf("Invalid build ID format: %s", idStr), - Detail: err.Error(), - }) - return - } - buildIDs = append(buildIDs, id) - } - - parameters, err := api.Database.GetWorkspaceBuildParametersByBuildIDs(ctx, buildIDs) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace build parameters.", - Detail: err.Error(), - }) - return - } - - promptsByBuildID := make(map[string]string, len(parameters)) - for _, param := range parameters { - if param.Name != codersdk.AITaskPromptParameterName { - continue - } - buildID := param.WorkspaceBuildID.String() - promptsByBuildID[buildID] = param.Value - } - - httpapi.Write(ctx, rw, http.StatusOK, codersdk.AITasksPromptsResponse{ - Prompts: promptsByBuildID, - }) -} - // @Summary Create a new AI task // @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable. // @ID create-task @@ -174,13 +123,31 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) { } } + // Check if the template defines the AI Prompt parameter. + templateParams, err := api.Database.GetTemplateVersionParameters(ctx, req.TemplateVersionID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching template parameters.", + Detail: err.Error(), + }) + return + } + + var richParams []codersdk.WorkspaceBuildParameter + if _, hasAIPromptParam := slice.Find(templateParams, func(param database.TemplateVersionParameter) bool { + return param.Name == codersdk.AITaskPromptParameterName + }); hasAIPromptParam { + // Only add the AI Prompt parameter if the template defines it. + richParams = []codersdk.WorkspaceBuildParameter{ + {Name: codersdk.AITaskPromptParameterName, Value: req.Input}, + } + } + createReq := codersdk.CreateWorkspaceRequest{ Name: taskName, TemplateVersionID: req.TemplateVersionID, TemplateVersionPresetID: req.TemplateVersionPresetID, - RichParameterValues: []codersdk.WorkspaceBuildParameter{ - {Name: codersdk.AITaskPromptParameterName, Value: req.Input}, - }, + RichParameterValues: richParams, } var owner workspaceOwner diff --git a/coderd/aitasks_test.go b/coderd/aitasks_test.go index d3b5e240d8301..9fa8e168a4ecb 100644 --- a/coderd/aitasks_test.go +++ b/coderd/aitasks_test.go @@ -35,128 +35,6 @@ import ( "github.com/coder/coder/v2/testutil" ) -func TestAITasksPrompts(t *testing.T) { - t.Parallel() - - t.Run("EmptyBuildIDs", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{}) - _ = coderdtest.CreateFirstUser(t, client) - experimentalClient := codersdk.NewExperimentalClient(client) - - ctx := testutil.Context(t, testutil.WaitShort) - - // Test with empty build IDs - prompts, err := experimentalClient.AITaskPrompts(ctx, []uuid.UUID{}) - require.NoError(t, err) - require.Empty(t, prompts.Prompts) - }) - - t.Run("MultipleBuilds", func(t *testing.T) { - t.Parallel() - - adminClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) - first := coderdtest.CreateFirstUser(t, adminClient) - memberClient, _ := coderdtest.CreateAnotherUser(t, adminClient, first.OrganizationID) - - ctx := testutil.Context(t, testutil.WaitLong) - - // Create a template with parameters - version := coderdtest.CreateTemplateVersion(t, adminClient, first.OrganizationID, &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionPlan: []*proto.Response{{ - Type: &proto.Response_Plan{ - Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{ - { - Name: "param1", - Type: "string", - DefaultValue: "default1", - }, - { - Name: codersdk.AITaskPromptParameterName, - Type: "string", - DefaultValue: "default2", - }, - }, - }, - }, - }}, - ProvisionApply: echo.ApplyComplete, - }) - template := coderdtest.CreateTemplate(t, adminClient, first.OrganizationID, version.ID) - coderdtest.AwaitTemplateVersionJobCompleted(t, adminClient, version.ID) - - // Create two workspaces with different parameters - workspace1 := coderdtest.CreateWorkspace(t, memberClient, template.ID, func(request *codersdk.CreateWorkspaceRequest) { - request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ - {Name: "param1", Value: "value1a"}, - {Name: codersdk.AITaskPromptParameterName, Value: "value2a"}, - } - }) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, memberClient, workspace1.LatestBuild.ID) - - workspace2 := coderdtest.CreateWorkspace(t, memberClient, template.ID, func(request *codersdk.CreateWorkspaceRequest) { - request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ - {Name: "param1", Value: "value1b"}, - {Name: codersdk.AITaskPromptParameterName, Value: "value2b"}, - } - }) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, memberClient, workspace2.LatestBuild.ID) - - workspace3 := coderdtest.CreateWorkspace(t, adminClient, template.ID, func(request *codersdk.CreateWorkspaceRequest) { - request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ - {Name: "param1", Value: "value1c"}, - {Name: codersdk.AITaskPromptParameterName, Value: "value2c"}, - } - }) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, adminClient, workspace3.LatestBuild.ID) - allBuildIDs := []uuid.UUID{workspace1.LatestBuild.ID, workspace2.LatestBuild.ID, workspace3.LatestBuild.ID} - - experimentalMemberClient := codersdk.NewExperimentalClient(memberClient) - // Test parameters endpoint as member - prompts, err := experimentalMemberClient.AITaskPrompts(ctx, allBuildIDs) - require.NoError(t, err) - // we expect 2 prompts because the member client does not have access to workspace3 - // since it was created by the admin client - require.Len(t, prompts.Prompts, 2) - - // Check workspace1 parameters - build1Prompt := prompts.Prompts[workspace1.LatestBuild.ID.String()] - require.Equal(t, "value2a", build1Prompt) - - // Check workspace2 parameters - build2Prompt := prompts.Prompts[workspace2.LatestBuild.ID.String()] - require.Equal(t, "value2b", build2Prompt) - - experimentalAdminClient := codersdk.NewExperimentalClient(adminClient) - // Test parameters endpoint as admin - // we expect 3 prompts because the admin client has access to all workspaces - prompts, err = experimentalAdminClient.AITaskPrompts(ctx, allBuildIDs) - require.NoError(t, err) - require.Len(t, prompts.Prompts, 3) - - // Check workspace3 parameters - build3Prompt := prompts.Prompts[workspace3.LatestBuild.ID.String()] - require.Equal(t, "value2c", build3Prompt) - }) - - t.Run("NonExistentBuildIDs", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{}) - _ = coderdtest.CreateFirstUser(t, client) - - ctx := testutil.Context(t, testutil.WaitShort) - - // Test with non-existent build IDs - nonExistentID := uuid.New() - experimentalClient := codersdk.NewExperimentalClient(client) - prompts, err := experimentalClient.AITaskPrompts(ctx, []uuid.UUID{nonExistentID}) - require.NoError(t, err) - require.Empty(t, prompts.Prompts) - }) -} - func TestTasks(t *testing.T) { t.Parallel() @@ -188,7 +66,6 @@ func TestTasks(t *testing.T) { { Type: &proto.Response_Plan{ Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}}, HasAiTasks: true, }, }, @@ -817,6 +694,51 @@ func TestTasksCreate(t *testing.T) { client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionApply: echo.ApplyComplete, + ProvisionPlan: []*proto.Response{ + {Type: &proto.Response_Plan{Plan: &proto.PlanComplete{ + HasAiTasks: true, + }}}, + }, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + + expClient := codersdk.NewExperimentalClient(client) + + task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{ + TemplateVersionID: template.ActiveVersionID, + Input: taskPrompt, + }) + require.NoError(t, err) + require.True(t, task.WorkspaceID.Valid) + + ws, err := client.Workspace(ctx, task.WorkspaceID.UUID) + require.NoError(t, err) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) + + assert.NotEmpty(t, task.Name) + assert.Equal(t, template.ID, task.TemplateID) + + parameters, err := client.WorkspaceBuildParameters(ctx, ws.LatestBuild.ID) + require.NoError(t, err) + require.Len(t, parameters, 0) + }) + + t.Run("OK AIPromptBackCompat", func(t *testing.T) { + t.Parallel() + + var ( + ctx = testutil.Context(t, testutil.WaitShort) + + taskPrompt = "Some task prompt" + ) + + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + // Given: A template with an "AI Prompt" parameter version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ Parse: echo.ParseComplete, @@ -896,7 +818,6 @@ func TestTasksCreate(t *testing.T) { ProvisionApply: echo.ApplyComplete, ProvisionPlan: []*proto.Response{ {Type: &proto.Response_Plan{Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}}, HasAiTasks: true, }}}, }, @@ -1012,7 +933,6 @@ func TestTasksCreate(t *testing.T) { ProvisionApply: echo.ApplyComplete, ProvisionPlan: []*proto.Response{ {Type: &proto.Response_Plan{Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}}, HasAiTasks: true, }}}, }, @@ -1072,7 +992,6 @@ func TestTasksCreate(t *testing.T) { ProvisionApply: echo.ApplyComplete, ProvisionPlan: []*proto.Response{ {Type: &proto.Response_Plan{Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}}, HasAiTasks: true, }}}, }, @@ -1109,7 +1028,6 @@ func TestTasksCreate(t *testing.T) { ProvisionApply: echo.ApplyComplete, ProvisionPlan: []*proto.Response{ {Type: &proto.Response_Plan{Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}}, HasAiTasks: true, }}}, }, @@ -1162,7 +1080,6 @@ func TestTasksCreate(t *testing.T) { ProvisionApply: echo.ApplyComplete, ProvisionPlan: []*proto.Response{ {Type: &proto.Response_Plan{Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}}, HasAiTasks: true, }}}, }, @@ -1175,7 +1092,6 @@ func TestTasksCreate(t *testing.T) { ProvisionApply: echo.ApplyComplete, ProvisionPlan: []*proto.Response{ {Type: &proto.Response_Plan{Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}}, HasAiTasks: true, }}}, }, diff --git a/coderd/coderd.go b/coderd/coderd.go index a1f94bfa6fee7..fd361c1e0e1de 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1021,10 +1021,7 @@ func New(options *Options) *API { apiRateLimiter, httpmw.ReportCLITelemetry(api.Logger, options.Telemetry), ) - r.Route("/aitasks", func(r chi.Router) { - r.Use(apiKeyMiddleware) - r.Get("/prompts", api.aiTasksPrompts) - }) + r.Route("/tasks", func(r chi.Router) { r.Use(apiKeyMiddleware) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index f9b058a40986e..46f9fb0f68ba1 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -329,7 +329,6 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa &i.LatestBuildError, &i.LatestBuildTransition, &i.LatestBuildStatus, - &i.LatestBuildHasAITask, &i.LatestBuildHasExternalAgent, &i.Count, ); err != nil { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index eafe73279b158..e9108751cf4a5 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -22289,7 +22289,6 @@ SELECT latest_build.error as latest_build_error, latest_build.transition as latest_build_transition, latest_build.job_status as latest_build_status, - latest_build.has_ai_task as latest_build_has_ai_task, latest_build.has_external_agent as latest_build_has_external_agent FROM workspaces_expanded as workspaces @@ -22523,25 +22522,19 @@ WHERE (latest_build.template_version_id = template.active_version_id) = $18 :: boolean ELSE true END - -- Filter by has_ai_task in latest build + -- Filter by has_ai_task, checks if this is a task workspace. AND CASE - WHEN $19 :: boolean IS NOT NULL THEN - (COALESCE(latest_build.has_ai_task, false) OR ( - -- If the build has no AI task, it means that the provisioner job is in progress - -- and we don't know if it has an AI task yet. In this case, we optimistically - -- assume that it has an AI task if the AI Prompt parameter is not empty. This - -- lets the AI Task frontend spawn a task and see it immediately after instead of - -- having to wait for the build to complete. - latest_build.has_ai_task IS NULL AND - latest_build.completed_at IS NULL AND - EXISTS ( - SELECT 1 - FROM workspace_build_parameters - WHERE workspace_build_parameters.workspace_build_id = latest_build.id - AND workspace_build_parameters.name = 'AI Prompt' - AND workspace_build_parameters.value != '' - ) - )) = ($19 :: boolean) + WHEN $19::boolean IS NOT NULL + THEN $19::boolean = EXISTS ( + SELECT + 1 + FROM + tasks + WHERE + -- Consider all tasks, deleting a task does not turn the + -- workspace into a non-task workspace. + tasks.workspace_id = workspaces.id + ) ELSE true END -- Filter by has_external_agent in latest build @@ -22572,7 +22565,7 @@ WHERE -- @authorize_filter ), filtered_workspaces_order AS ( SELECT - fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.task_id, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_ai_task, fw.latest_build_has_external_agent + fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.task_id, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_external_agent FROM filtered_workspaces fw ORDER BY @@ -22593,7 +22586,7 @@ WHERE $25 ), filtered_workspaces_order_with_summary AS ( SELECT - fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.task_id, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_ai_task, fwo.latest_build_has_external_agent + fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.task_id, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_external_agent FROM filtered_workspaces_order fwo -- Return a technical summary row with total count of workspaces. @@ -22638,7 +22631,6 @@ WHERE '', -- latest_build_error 'start'::workspace_transition, -- latest_build_transition 'unknown'::provisioner_job_status, -- latest_build_status - false, -- latest_build_has_ai_task false -- latest_build_has_external_agent WHERE $27 :: boolean = true @@ -22649,7 +22641,7 @@ WHERE filtered_workspaces ) SELECT - fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.task_id, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_ai_task, fwos.latest_build_has_external_agent, + fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.task_id, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_external_agent, tc.count FROM filtered_workspaces_order_with_summary fwos @@ -22725,7 +22717,6 @@ type GetWorkspacesRow struct { LatestBuildError sql.NullString `db:"latest_build_error" json:"latest_build_error"` LatestBuildTransition WorkspaceTransition `db:"latest_build_transition" json:"latest_build_transition"` LatestBuildStatus ProvisionerJobStatus `db:"latest_build_status" json:"latest_build_status"` - LatestBuildHasAITask sql.NullBool `db:"latest_build_has_ai_task" json:"latest_build_has_ai_task"` LatestBuildHasExternalAgent sql.NullBool `db:"latest_build_has_external_agent" json:"latest_build_has_external_agent"` Count int64 `db:"count" json:"count"` } @@ -22808,7 +22799,6 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) &i.LatestBuildError, &i.LatestBuildTransition, &i.LatestBuildStatus, - &i.LatestBuildHasAITask, &i.LatestBuildHasExternalAgent, &i.Count, ); err != nil { diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index d48285bb7de9c..9e6e0d8b24862 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -117,7 +117,6 @@ SELECT latest_build.error as latest_build_error, latest_build.transition as latest_build_transition, latest_build.job_status as latest_build_status, - latest_build.has_ai_task as latest_build_has_ai_task, latest_build.has_external_agent as latest_build_has_external_agent FROM workspaces_expanded as workspaces @@ -351,25 +350,19 @@ WHERE (latest_build.template_version_id = template.active_version_id) = sqlc.narg('using_active') :: boolean ELSE true END - -- Filter by has_ai_task in latest build + -- Filter by has_ai_task, checks if this is a task workspace. AND CASE - WHEN sqlc.narg('has_ai_task') :: boolean IS NOT NULL THEN - (COALESCE(latest_build.has_ai_task, false) OR ( - -- If the build has no AI task, it means that the provisioner job is in progress - -- and we don't know if it has an AI task yet. In this case, we optimistically - -- assume that it has an AI task if the AI Prompt parameter is not empty. This - -- lets the AI Task frontend spawn a task and see it immediately after instead of - -- having to wait for the build to complete. - latest_build.has_ai_task IS NULL AND - latest_build.completed_at IS NULL AND - EXISTS ( - SELECT 1 - FROM workspace_build_parameters - WHERE workspace_build_parameters.workspace_build_id = latest_build.id - AND workspace_build_parameters.name = 'AI Prompt' - AND workspace_build_parameters.value != '' - ) - )) = (sqlc.narg('has_ai_task') :: boolean) + WHEN sqlc.narg('has_ai_task')::boolean IS NOT NULL + THEN sqlc.narg('has_ai_task')::boolean = EXISTS ( + SELECT + 1 + FROM + tasks + WHERE + -- Consider all tasks, deleting a task does not turn the + -- workspace into a non-task workspace. + tasks.workspace_id = workspaces.id + ) ELSE true END -- Filter by has_external_agent in latest build @@ -466,7 +459,6 @@ WHERE '', -- latest_build_error 'start'::workspace_transition, -- latest_build_transition 'unknown'::provisioner_job_status, -- latest_build_status - false, -- latest_build_has_ai_task false -- latest_build_has_external_agent WHERE @with_summary :: boolean = true diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 51134dce27951..6515367bfe827 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -4700,11 +4700,16 @@ func TestWorkspaceFilterHasAITask(t *testing.T) { ctx := testutil.Context(t, testutil.WaitLong) - // Helper function to create workspace with AI task configuration - createWorkspaceWithAIConfig := func(hasAITask sql.NullBool, jobCompleted bool, aiTaskPrompt *string) database.WorkspaceTable { + // Helper function to create workspace with optional task. + createWorkspace := func(jobCompleted, createTask bool, prompt string) uuid.UUID { + // TODO(mafredri): The bellow comment is based on deprecated logic and + // kept only present to test that the old observable behavior works as + // intended. + // // When a provisioner job uses these tags, no provisioner will match it. - // We do this so jobs will always be stuck in "pending", allowing us to exercise the intermediary state when - // has_ai_task is nil and we compensate by looking at pending provisioning jobs. + // We do this so jobs will always be stuck in "pending", allowing us to + // exercise the intermediary state when has_ai_task is nil and we + // compensate by looking at pending provisioning jobs. // See GetWorkspaces clauses. unpickableTags := database.StringMap{"custom": "true"} @@ -4723,102 +4728,71 @@ func TestWorkspaceFilterHasAITask(t *testing.T) { jobConfig.CompletedAt = sql.NullTime{Time: time.Now(), Valid: true} } job := dbgen.ProvisionerJob(t, db, pubsub, jobConfig) - res := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{JobID: job.ID}) agnt := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ResourceID: res.ID}) - - var sidebarAppID uuid.UUID - if hasAITask.Bool { - sidebarApp := dbgen.WorkspaceApp(t, db, database.WorkspaceApp{AgentID: agnt.ID}) - sidebarAppID = sidebarApp.ID - } - + taskApp := dbgen.WorkspaceApp(t, db, database.WorkspaceApp{AgentID: agnt.ID}) build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{ WorkspaceID: ws.ID, TemplateVersionID: version.ID, InitiatorID: user.UserID, JobID: job.ID, BuildNumber: 1, - HasAITask: hasAITask, - AITaskSidebarAppID: uuid.NullUUID{UUID: sidebarAppID, Valid: sidebarAppID != uuid.Nil}, + AITaskSidebarAppID: uuid.NullUUID{UUID: taskApp.ID, Valid: createTask}, }) - if aiTaskPrompt != nil { - err := db.InsertWorkspaceBuildParameters(dbauthz.AsSystemRestricted(ctx), database.InsertWorkspaceBuildParametersParams{ - WorkspaceBuildID: build.ID, - Name: []string{provider.TaskPromptParameterName}, - Value: []string{*aiTaskPrompt}, + if createTask { + task := dbgen.Task(t, db, database.TaskTable{ + WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true}, + OrganizationID: user.OrganizationID, + OwnerID: user.UserID, + TemplateVersionID: version.ID, + Prompt: prompt, + }) + dbgen.TaskWorkspaceApp(t, db, database.TaskWorkspaceApp{ + TaskID: task.ID, + WorkspaceBuildNumber: build.BuildNumber, + WorkspaceAgentID: uuid.NullUUID{UUID: agnt.ID, Valid: true}, + WorkspaceAppID: uuid.NullUUID{UUID: taskApp.ID, Valid: true}, }) - require.NoError(t, err) } - return ws + return ws.ID } - // Create test workspaces with different AI task configurations - wsWithAITask := createWorkspaceWithAIConfig(sql.NullBool{Bool: true, Valid: true}, true, nil) - wsWithoutAITask := createWorkspaceWithAIConfig(sql.NullBool{Bool: false, Valid: true}, false, nil) - - aiTaskPrompt := "Build me a web app" - wsWithAITaskParam := createWorkspaceWithAIConfig(sql.NullBool{Valid: false}, false, &aiTaskPrompt) + // Create workspaces with tasks. + wsWithTask1 := createWorkspace(true, true, "Build me a web app") + wsWithTask2 := createWorkspace(false, true, "Another task") - anotherTaskPrompt := "Another task" - wsCompletedWithAITaskParam := createWorkspaceWithAIConfig(sql.NullBool{Valid: false}, true, &anotherTaskPrompt) - - emptyPrompt := "" - wsWithEmptyAITaskParam := createWorkspaceWithAIConfig(sql.NullBool{Valid: false}, false, &emptyPrompt) - - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() - - // Debug: Check all workspaces without filter first - allRes, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{}) - require.NoError(t, err) - t.Logf("Total workspaces created: %d", len(allRes.Workspaces)) - for i, ws := range allRes.Workspaces { - t.Logf("All Workspace %d: ID=%s, Name=%s, Build ID=%s, Job ID=%s", i, ws.ID, ws.Name, ws.LatestBuild.ID, ws.LatestBuild.Job.ID) - } + // Create workspaces without tasks + wsWithoutTask1 := createWorkspace(true, false, "") + wsWithoutTask2 := createWorkspace(false, false, "") // Test filtering for workspaces with AI tasks - // Should include: wsWithAITask (has_ai_task=true) and wsWithAITaskParam (null + incomplete + param) + // Should include: wsWithTask1 and wsWithTask2 res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ FilterQuery: "has-ai-task:true", }) require.NoError(t, err) - t.Logf("Expected 2 workspaces for has-ai-task:true, got %d", len(res.Workspaces)) - t.Logf("Expected workspaces: %s, %s", wsWithAITask.ID, wsWithAITaskParam.ID) - for i, ws := range res.Workspaces { - t.Logf("AI Task True Workspace %d: ID=%s, Name=%s", i, ws.ID, ws.Name) - } require.Len(t, res.Workspaces, 2) workspaceIDs := []uuid.UUID{res.Workspaces[0].ID, res.Workspaces[1].ID} - require.Contains(t, workspaceIDs, wsWithAITask.ID) - require.Contains(t, workspaceIDs, wsWithAITaskParam.ID) + require.Contains(t, workspaceIDs, wsWithTask1) + require.Contains(t, workspaceIDs, wsWithTask2) // Test filtering for workspaces without AI tasks - // Should include: wsWithoutAITask, wsCompletedWithAITaskParam, wsWithEmptyAITaskParam + // Should include: wsWithoutTask1, wsWithoutTask2, wsWithoutTask3 res, err = client.Workspaces(ctx, codersdk.WorkspaceFilter{ FilterQuery: "has-ai-task:false", }) require.NoError(t, err) - - // Debug: print what we got - t.Logf("Expected 3 workspaces for has-ai-task:false, got %d", len(res.Workspaces)) - for i, ws := range res.Workspaces { - t.Logf("Workspace %d: ID=%s, Name=%s", i, ws.ID, ws.Name) - } - t.Logf("Expected IDs: %s, %s, %s", wsWithoutAITask.ID, wsCompletedWithAITaskParam.ID, wsWithEmptyAITaskParam.ID) - - require.Len(t, res.Workspaces, 3) - workspaceIDs = []uuid.UUID{res.Workspaces[0].ID, res.Workspaces[1].ID, res.Workspaces[2].ID} - require.Contains(t, workspaceIDs, wsWithoutAITask.ID) - require.Contains(t, workspaceIDs, wsCompletedWithAITaskParam.ID) - require.Contains(t, workspaceIDs, wsWithEmptyAITaskParam.ID) + require.Len(t, res.Workspaces, 2) + workspaceIDs = []uuid.UUID{res.Workspaces[0].ID, res.Workspaces[1].ID} + require.Contains(t, workspaceIDs, wsWithoutTask1) + require.Contains(t, workspaceIDs, wsWithoutTask2) // Test no filter returns all res, err = client.Workspaces(ctx, codersdk.WorkspaceFilter{}) require.NoError(t, err) - require.Len(t, res.Workspaces, 5) + require.Len(t, res.Workspaces, 4) } func TestWorkspaceAppUpsertRestart(t *testing.T) { diff --git a/codersdk/aitasks.go b/codersdk/aitasks.go index 9f390202e4fd2..1f1e9758e9d99 100644 --- a/codersdk/aitasks.go +++ b/codersdk/aitasks.go @@ -17,45 +17,15 @@ import ( // AITaskPromptParameterName is the name of the parameter used to pass prompts // to AI tasks. // -// Experimental: This value is experimental and may change in the future. -const AITaskPromptParameterName = provider.TaskPromptParameterName - -// AITasksPromptsResponse represents the response from the AITaskPrompts method. +// Deprecated: This constant is deprecated and maintained only for backwards +// compatibility with older templates. Task prompts are now stored directly +// in the tasks.prompt database column. New code should access prompts via +// the Task.InitialPrompt field returned from task endpoints. // -// Experimental: This method is experimental and may change in the future. -type AITasksPromptsResponse struct { - // Prompts is a map of workspace build IDs to prompts. - Prompts map[string]string `json:"prompts"` -} - -// AITaskPrompts returns prompts for multiple workspace builds by their IDs. -// -// Experimental: This method is experimental and may change in the future. -func (c *ExperimentalClient) AITaskPrompts(ctx context.Context, buildIDs []uuid.UUID) (AITasksPromptsResponse, error) { - if len(buildIDs) == 0 { - return AITasksPromptsResponse{ - Prompts: make(map[string]string), - }, nil - } - - // Convert UUIDs to strings and join them - buildIDStrings := make([]string, len(buildIDs)) - for i, id := range buildIDs { - buildIDStrings[i] = id.String() - } - buildIDsParam := strings.Join(buildIDStrings, ",") - - res, err := c.Request(ctx, http.MethodGet, "/api/experimental/aitasks/prompts", nil, WithQueryParam("build_ids", buildIDsParam)) - if err != nil { - return AITasksPromptsResponse{}, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return AITasksPromptsResponse{}, ReadBodyAsError(res) - } - var prompts AITasksPromptsResponse - return prompts, json.NewDecoder(res.Body).Decode(&prompts) -} +// This constant will be removed in a future major version. Templates should +// not rely on this parameter name, as the backend will continue to create it +// automatically for compatibility but reads from tasks.prompt. +const AITaskPromptParameterName = provider.TaskPromptParameterName // CreateTaskRequest represents the request to create a new task. // diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 4c02a96fe2129..baf56e5a27584 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -2661,27 +2661,6 @@ export type CreateTaskFeedbackRequest = { class ExperimentalApiMethods { constructor(protected readonly axios: AxiosInstance) {} - getAITasksPrompts = async ( - buildIds: TypesGen.WorkspaceBuild["id"][], - ): Promise => { - if (buildIds.length === 0) { - return { - prompts: {}, - }; - } - - const response = await this.axios.get( - "/api/experimental/aitasks/prompts", - { - params: { - build_ids: buildIds.join(","), - }, - }, - ); - - return response.data; - }; - createTask = async ( user: string, req: TypesGen.CreateTaskRequest, diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 6d703cbcfe2d6..90913ea8424b4 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -108,22 +108,16 @@ export interface AIConfig { * AITaskPromptParameterName is the name of the parameter used to pass prompts * to AI tasks. * - * Experimental: This value is experimental and may change in the future. - */ -export const AITaskPromptParameterName = "AI Prompt"; - -// From codersdk/aitasks.go -/** - * AITasksPromptsResponse represents the response from the AITaskPrompts method. + * Deprecated: This constant is deprecated and maintained only for backwards + * compatibility with older templates. Task prompts are now stored directly + * in the tasks.prompt database column. New code should access prompts via + * the Task.InitialPrompt field returned from task endpoints. * - * Experimental: This method is experimental and may change in the future. + * This constant will be removed in a future major version. Templates should + * not rely on this parameter name, as the backend will continue to create it + * automatically for compatibility but reads from tasks.prompt. */ -export interface AITasksPromptsResponse { - /** - * Prompts is a map of workspace build IDs to prompts. - */ - readonly prompts: Record; -} +export const AITaskPromptParameterName = "AI Prompt"; // From codersdk/allowlist.go /** diff --git a/site/src/modules/tasks/TaskPrompt/TaskPrompt.tsx b/site/src/modules/tasks/TaskPrompt/TaskPrompt.tsx index b40f3253a8e93..e0afb9cf7d02f 100644 --- a/site/src/modules/tasks/TaskPrompt/TaskPrompt.tsx +++ b/site/src/modules/tasks/TaskPrompt/TaskPrompt.tsx @@ -7,6 +7,7 @@ import type { Template, TemplateVersionExternalAuth, } from "api/typesGenerated"; +import { AITaskPromptParameterName } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Button } from "components/Button/Button"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; @@ -38,8 +39,6 @@ import { docs } from "utils/docs"; import { PromptSelectTrigger } from "./PromptSelectTrigger"; import { TemplateVersionSelect } from "./TemplateVersionSelect"; -const AI_PROMPT_PARAMETER_NAME = "AI Prompt"; - type TaskPromptProps = { templates: Template[] | undefined; error: unknown; @@ -168,7 +167,7 @@ const CreateTaskForm: FC = ({ templates, onSuccess }) => { // Read-only prompt if defined in preset const presetPrompt = selectedPreset?.Parameters?.find( - (param) => param.Name === AI_PROMPT_PARAMETER_NAME, + (param) => param.Name === AITaskPromptParameterName, )?.Value; const isPromptReadOnly = !!presetPrompt; useEffect(() => { From 1b6556c2f69aa47b5e530ed4f667207ee3ecefdb Mon Sep 17 00:00:00 2001 From: Bartek Gatz Date: Wed, 29 Oct 2025 14:00:27 -0500 Subject: [PATCH 020/255] fix: improve visual separation between prompt and task list (#20427) --- site/src/pages/TasksPage/TasksPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/TasksPage/TasksPage.tsx b/site/src/pages/TasksPage/TasksPage.tsx index 0d7dee5bb7ace..f87c6af39f54d 100644 --- a/site/src/pages/TasksPage/TasksPage.tsx +++ b/site/src/pages/TasksPage/TasksPage.tsx @@ -69,7 +69,7 @@ const TasksPage: FC = () => { onRetry={aiTemplatesQuery.refetch} /> {aiTemplatesQuery.isSuccess && ( -
+
{permissions.viewDeploymentConfig && (
Date: Wed, 29 Oct 2025 19:41:19 +0000 Subject: [PATCH 021/255] refactor: migrate deployment banner to Tailwind and radix (#20479) before: Screenshot 2025-10-25 at 18 02 45 after: Screenshot 2025-10-25 at 18 02 17 --- site/src/components/Link/Link.tsx | 8 +- .../DeploymentBannerView.stories.tsx | 17 + .../DeploymentBanner/DeploymentBannerView.tsx | 484 +++++++++--------- 3 files changed, 252 insertions(+), 257 deletions(-) diff --git a/site/src/components/Link/Link.tsx b/site/src/components/Link/Link.tsx index c68ac1d70d8f7..34af1b71e1a3d 100644 --- a/site/src/components/Link/Link.tsx +++ b/site/src/components/Link/Link.tsx @@ -27,10 +27,14 @@ interface LinkProps extends React.AnchorHTMLAttributes, VariantProps { asChild?: boolean; + showExternalIcon?: boolean; } export const Link = forwardRef( - ({ className, children, size, asChild, ...props }, ref) => { + ( + { className, children, size, asChild, showExternalIcon = true, ...props }, + ref, + ) => { const Comp = asChild ? Slot : "a"; return ( ( {...props} > {children} - ); }, diff --git a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.stories.tsx b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.stories.tsx index 949aa0390e573..b5bb44f76d141 100644 --- a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.stories.tsx +++ b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.stories.tsx @@ -3,6 +3,7 @@ import { MockDeploymentStats, } from "testHelpers/entities"; import type { Meta, StoryObj } from "@storybook/react-vite"; +import { expect, screen, userEvent, waitFor, within } from "storybook/test"; import { DeploymentBannerView } from "./DeploymentBannerView"; const meta: Meta = { @@ -22,6 +23,14 @@ export const WithHealthIssues: Story = { args: { health: DeploymentHealthUnhealthy, }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByTestId("deployment-health-trigger"); + await userEvent.hover(trigger); + await waitFor(() => + expect(screen.getByRole("tooltip")).toBeInTheDocument(), + ); + }, }; export const WithDismissedHealthIssues: Story = { @@ -34,4 +43,12 @@ export const WithDismissedHealthIssues: Story = { }, }, }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByTestId("deployment-health-trigger"); + await userEvent.hover(trigger); + await waitFor(() => + expect(screen.getByRole("tooltip")).toBeInTheDocument(), + ); + }, }; diff --git a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx index eccd9ad6b62bb..de367f12c199a 100644 --- a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx +++ b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx @@ -1,6 +1,3 @@ -import { css, type Interpolation, type Theme, useTheme } from "@emotion/react"; -import Link from "@mui/material/Link"; -import Tooltip from "@mui/material/Tooltip"; import type { DeploymentStats, HealthcheckReport, @@ -12,7 +9,13 @@ import { JetBrainsIcon } from "components/Icons/JetBrainsIcon"; import { RocketIcon } from "components/Icons/RocketIcon"; import { TerminalIcon } from "components/Icons/TerminalIcon"; import { VSCodeIcon } from "components/Icons/VSCodeIcon"; -import { Stack } from "components/Stack/Stack"; +import { Link } from "components/Link/Link"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import dayjs from "dayjs"; import { AppWindowIcon, @@ -33,12 +36,8 @@ import { useState, } from "react"; import { Link as RouterLink } from "react-router"; -import { MONOSPACE_FONT_FAMILY } from "theme/constants"; -import colors from "theme/tailwindColors"; import { getDisplayWorkspaceStatus } from "utils/workspace"; -const bannerHeight = 36; - interface DeploymentBannerViewProps { health?: HealthcheckReport; stats?: DeploymentStats; @@ -50,8 +49,6 @@ export const DeploymentBannerView: FC = ({ stats, fetchStats, }) => { - const theme = useTheme(); - const aggregatedMinutes = useMemo(() => { if (!stats) { return; @@ -105,67 +102,60 @@ export const DeploymentBannerView: FC = ({ return (
- 0 ? ( - <> - - We have detected problems with your Coder deployment. - - - {healthErrors.map((error) => ( - {error} - ))} - - - ) : ( - "Status of your Coder deployment. Only visible for admins!" - ) - } - open={process.env.STORYBOOK === "true" ? true : undefined} - css={{ marginRight: -16 }} - > - {healthErrors.length > 0 ? ( - + + + {healthErrors.length > 0 ? ( + + + + + + ) : ( +
+ +
+ )} +
+ - - - ) : ( -
- -
- )} -
+ {healthErrors.length > 0 ? ( + <> + + We have detected problems with your Coder deployment. + +
+ {healthErrors.map((error) => ( + {error} + ))} +
+ + ) : ( + "Status of your Coder deployment. Only visible for admins!" + )} + +
+ -
-
Workspaces
-
+
+
Workspaces
+
= ({
-
- -
Transmission
-
- -
- -
- - {stats ? prettyBytes(stats.workspaces.rx_bytes) : "-"} -
+
+ + + +
Transmission
+
+ + {`Activity in the last ~${aggregatedMinutes} minutes`} +
+
+
+ + + +
+ + {stats ? prettyBytes(stats.workspaces.rx_bytes) : "-"} +
+
+ Data sent to workspaces +
+
- -
- - {stats ? prettyBytes(stats.workspaces.tx_bytes) : "-"} -
-
+ + + +
+ + {stats ? prettyBytes(stats.workspaces.tx_bytes) : "-"} +
+
+ Data sent from workspaces +
+
- -
- - {displayLatency > 0 ? `${displayLatency?.toFixed(2)} ms` : "-"} -
-
+ + + +
+ + {displayLatency > 0 + ? `${displayLatency?.toFixed(2)} ms` + : "-"} +
+
+ + {displayLatency < 0 + ? "No recent workspace connections have been made" + : "The average latency of user connections to workspaces"} + +
+
-
-
Active Connections
+
+
Active Connections
-
- -
- - {typeof stats?.session_count.vscode === "undefined" - ? "-" - : stats?.session_count.vscode} -
-
+
+ + + +
+ + {typeof stats?.session_count.vscode === "undefined" + ? "-" + : stats?.session_count.vscode} +
+
+ + VS Code Editors with the Coder Remote Extension + +
+
- -
- - {typeof stats?.session_count.jetbrains === "undefined" - ? "-" - : stats?.session_count.jetbrains} -
-
+ + + +
+ + {typeof stats?.session_count.jetbrains === "undefined" + ? "-" + : stats?.session_count.jetbrains} +
+
+ JetBrains Editors +
+
- -
- - {typeof stats?.session_count.ssh === "undefined" - ? "-" - : stats?.session_count.ssh} -
-
+ + + +
+ + {typeof stats?.session_count.ssh === "undefined" + ? "-" + : stats?.session_count.ssh} +
+
+ SSH Sessions +
+
- -
- - {typeof stats?.session_count.reconnecting_pty === "undefined" - ? "-" - : stats?.session_count.reconnecting_pty} -
-
+ + + +
+ + {typeof stats?.session_count.reconnecting_pty === "undefined" + ? "-" + : stats?.session_count.reconnecting_pty} +
+
+ Web Terminal Sessions +
+
-
- -
- - {lastAggregated} -
-
- - - - + + + + + + + A countdown until stats are fetched again. Click to refresh! + + +
); @@ -352,35 +370,36 @@ const WorkspaceBuildValue: FC = ({ } return ( - - -
- {icon} - {typeof count === "undefined" ? "-" : count} -
- -
+ + + + + +
+ {icon} + {typeof count === "undefined" ? "-" : count} +
+
+ +
+ {`${statusText} Workspaces`} +
+
); }; const ValueSeparator: FC = () => { - return
/
; + return
/
; }; const HealthIssue: FC = ({ children }) => { - const theme = useTheme(); - return ( - - +
+ {children} - +
); }; @@ -409,48 +428,3 @@ const getHealthErrors = (health: HealthcheckReport) => { return warnings; }; - -const styles = { - statusBadge: (theme) => css` - display: flex; - align-items: center; - justify-content: center; - padding: 0 12px; - height: 100%; - color: ${theme.experimental.l1.text}; - - & svg { - width: 16px; - height: 16px; - } - `, - unhealthy: { - backgroundColor: colors.red[700], - }, - group: css` - display: flex; - align-items: center; - `, - category: (theme) => ({ - marginRight: 16, - color: theme.palette.text.primary, - }), - values: (theme) => ({ - display: "flex", - gap: 8, - color: theme.palette.text.secondary, - }), - value: css` - display: flex; - align-items: center; - gap: 4px; - - & svg { - width: 12px; - height: 12px; - } - `, - separator: (theme) => ({ - color: theme.palette.text.disabled, - }), -} satisfies Record>; From 643fe38b1e31abb4e3840d3345a295659d928b60 Mon Sep 17 00:00:00 2001 From: Asher Date: Wed, 29 Oct 2025 12:23:06 -0800 Subject: [PATCH 022/255] fix: use temp file on same device with mcp file edit (#20477) Otherwise you can get errors like "invalid cross-device link". --- agent/files.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agent/files.go b/agent/files.go index f2a9ac6edc581..4ac707c602419 100644 --- a/agent/files.go +++ b/agent/files.go @@ -250,7 +250,9 @@ func (a *agent) editFile(ctx context.Context, path string, edits []workspacesdk. transforms[i] = replace.String(edit.Search, edit.Replace) } - tmpfile, err := afero.TempFile(a.filesystem, "", filepath.Base(path)) + // Create an adjacent file to ensure it will be on the same device and can be + // moved atomically. + tmpfile, err := afero.TempFile(a.filesystem, filepath.Dir(path), filepath.Base(path)) if err != nil { return http.StatusInternalServerError, err } From 9629d873fbc7b9ee00f31e3da339a72bac2aba23 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 29 Oct 2025 21:11:44 +0000 Subject: [PATCH 023/255] fix(site): fix disappearing preset selector when switching task template (#20514) Closes https://github.com/coder/coder/issues/20465 Ensure we set `selectedPresetId` to `undefined` when we change `selectedTemplateId` to ensure we don't end up breaking the ` setSelectedTemplateId(value)} + onValueChange={(value) => { + setSelectedTemplateId(value); + if (value !== selectedTemplateId) { + setSelectedPresetId(undefined); + } + }} defaultValue={templates[0].id} required > From 54497f4f6b624cd39a2c749b4dde91c07370abc9 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 29 Oct 2025 16:44:53 -0500 Subject: [PATCH 024/255] chore: add revocation endpoint to oauth well-known (#20561) Was added to apps endpoints, but not the wider site ones. This is a site wide oauth route --- coderd/apidoc/docs.go | 3 +++ coderd/apidoc/swagger.json | 3 +++ coderd/oauth2provider/metadata.go | 1 + codersdk/oauth2.go | 1 + docs/reference/api/enterprise.md | 1 + docs/reference/api/schemas.md | 2 ++ site/src/api/typesGenerated.ts | 1 + 7 files changed, 12 insertions(+) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index e459b94b3fdaf..2fe716a0775f6 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -15435,6 +15435,9 @@ const docTemplate = `{ "type": "string" } }, + "revocation_endpoint": { + "type": "string" + }, "scopes_supported": { "type": "array", "items": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 8eef9dfb8e0d7..10031b2dd36be 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -13989,6 +13989,9 @@ "type": "string" } }, + "revocation_endpoint": { + "type": "string" + }, "scopes_supported": { "type": "array", "items": { diff --git a/coderd/oauth2provider/metadata.go b/coderd/oauth2provider/metadata.go index a6edc4006bc1d..ecc80049df279 100644 --- a/coderd/oauth2provider/metadata.go +++ b/coderd/oauth2provider/metadata.go @@ -18,6 +18,7 @@ func GetAuthorizationServerMetadata(accessURL *url.URL) http.HandlerFunc { AuthorizationEndpoint: accessURL.JoinPath("/oauth2/authorize").String(), TokenEndpoint: accessURL.JoinPath("/oauth2/tokens").String(), RegistrationEndpoint: accessURL.JoinPath("/oauth2/register").String(), // RFC 7591 + RevocationEndpoint: accessURL.JoinPath("/oauth2/revoke").String(), // RFC 7009 ResponseTypesSupported: []string{"code"}, GrantTypesSupported: []string{"authorization_code", "refresh_token"}, CodeChallengeMethodsSupported: []string{"S256"}, diff --git a/codersdk/oauth2.go b/codersdk/oauth2.go index 79b2186480b9c..6b4d220df0a46 100644 --- a/codersdk/oauth2.go +++ b/codersdk/oauth2.go @@ -262,6 +262,7 @@ type OAuth2AuthorizationServerMetadata struct { AuthorizationEndpoint string `json:"authorization_endpoint"` TokenEndpoint string `json:"token_endpoint"` RegistrationEndpoint string `json:"registration_endpoint,omitempty"` + RevocationEndpoint string `json:"revocation_endpoint,omitempty"` ResponseTypesSupported []string `json:"response_types_supported"` GrantTypesSupported []string `json:"grant_types_supported"` CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` diff --git a/docs/reference/api/enterprise.md b/docs/reference/api/enterprise.md index 131223e38e5f4..5ab32d8a3f43b 100644 --- a/docs/reference/api/enterprise.md +++ b/docs/reference/api/enterprise.md @@ -30,6 +30,7 @@ curl -X GET http://coder-server:8080/api/v2/.well-known/oauth-authorization-serv "response_types_supported": [ "string" ], + "revocation_endpoint": "string", "scopes_supported": [ "string" ], diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 4317324f002be..300b79b6f435a 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -5322,6 +5322,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit| "response_types_supported": [ "string" ], + "revocation_endpoint": "string", "scopes_supported": [ "string" ], @@ -5342,6 +5343,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit| | `issuer` | string | false | | | | `registration_endpoint` | string | false | | | | `response_types_supported` | array of string | false | | | +| `revocation_endpoint` | string | false | | | | `scopes_supported` | array of string | false | | | | `token_endpoint` | string | false | | | | `token_endpoint_auth_methods_supported` | array of string | false | | | diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 90913ea8424b4..1c5d101e2676d 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2913,6 +2913,7 @@ export interface OAuth2AuthorizationServerMetadata { readonly authorization_endpoint: string; readonly token_endpoint: string; readonly registration_endpoint?: string; + readonly revocation_endpoint?: string; readonly response_types_supported: readonly string[]; readonly grant_types_supported: readonly string[]; readonly code_challenge_methods_supported: readonly string[]; From ff532d9bf36bd926e50ebdcb7d0c912b74bde7a4 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 29 Oct 2025 19:11:34 -0600 Subject: [PATCH 025/255] chore: handle deprecated `aibridge` experimental routes (#20565) In v2.28 we're [removing the aibridge experiment](https://github.com/coder/coder/pull/20544). We need to handle `/api/experimental/aibridge/*` until Beta (next release). Signed-off-by: Danny Kopping --- enterprise/coderd/aibridge.go | 34 +++++++++++++++ enterprise/coderd/aibridge_test.go | 66 ++++++++++++++++++++++++++++++ enterprise/coderd/coderd.go | 26 ++++-------- 3 files changed, 107 insertions(+), 19 deletions(-) diff --git a/enterprise/coderd/aibridge.go b/enterprise/coderd/aibridge.go index bdd2a99166910..3f3b1d6789a09 100644 --- a/enterprise/coderd/aibridge.go +++ b/enterprise/coderd/aibridge.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "net/http" + "strings" + "github.com/go-chi/chi/v5" "github.com/google/uuid" "golang.org/x/xerrors" @@ -23,6 +25,38 @@ const ( defaultListInterceptionsLimit = 100 ) +// aibridgeHandler handles all aibridged-related endpoints. +func aibridgeHandler(api *API, middlewares ...func(http.Handler) http.Handler) func(r chi.Router) { + return func(r chi.Router) { + r.Use(api.RequireFeatureMW(codersdk.FeatureAIBridge)) + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Get("/interceptions", api.aiBridgeListInterceptions) + }) + + // This is a bit funky but since aibridge only exposes a HTTP + // handler, this is how it has to be. + r.HandleFunc("/*", func(rw http.ResponseWriter, r *http.Request) { + if api.aibridgedHandler == nil { + httpapi.Write(r.Context(), rw, http.StatusNotFound, codersdk.Response{ + Message: "aibridged handler not mounted", + }) + return + } + + // Strip either the experimental or stable prefix. + // TODO: experimental route is deprecated and must be removed with Beta. + prefixes := []string{"/api/experimental/aibridge", "/api/v2/aibridge"} + for _, prefix := range prefixes { + if strings.Contains(r.URL.String(), prefix) { + http.StripPrefix(prefix, api.aibridgedHandler).ServeHTTP(rw, r) + break + } + } + }) + } +} + // aiBridgeListInterceptions returns all AIBridge interceptions a user can read. // Optional filters with query params // diff --git a/enterprise/coderd/aibridge_test.go b/enterprise/coderd/aibridge_test.go index 17e5df56fb65d..db1698f35311a 100644 --- a/enterprise/coderd/aibridge_test.go +++ b/enterprise/coderd/aibridge_test.go @@ -1,6 +1,7 @@ package coderd_test import ( + "io" "net/http" "testing" "time" @@ -592,3 +593,68 @@ func TestAIBridgeListInterceptions(t *testing.T) { } }) } + +func TestAIBridgeRouting(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + client, closer, api, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + DeploymentValues: dv, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAIBridge: 1, + }, + }, + }) + t.Cleanup(func() { + _ = closer.Close() + }) + + // Register a simple test handler that echoes back the request path. + testHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.WriteHeader(http.StatusOK) + _, _ = rw.Write([]byte(r.URL.Path)) + }) + api.RegisterInMemoryAIBridgedHTTPHandler(testHandler) + + cases := []struct { + name string + path string + expectedPath string + }{ + { + name: "StablePrefix", + path: "/api/v2/aibridge/openai/v1/chat/completions", + expectedPath: "/openai/v1/chat/completions", + }, + { + name: "ExperimentalPrefix", + path: "/api/experimental/aibridge/openai/v1/chat/completions", + expectedPath: "/openai/v1/chat/completions", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, client.URL.String()+tc.path, nil) + require.NoError(t, err) + req.Header.Set(codersdk.SessionTokenHeader, client.SessionToken()) + + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + + // Verify that the prefix was stripped correctly and the path was forwarded. + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, tc.expectedPath, string(body)) + }) + } +} diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index a4adb0479b96b..00a78c0bd9069 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -226,26 +226,14 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { return api.refreshEntitlements(ctx) } - api.AGPL.APIHandler.Group(func(r chi.Router) { - r.Route("/aibridge", func(r chi.Router) { - r.Use(api.RequireFeatureMW(codersdk.FeatureAIBridge)) - r.Group(func(r chi.Router) { - r.Use(apiKeyMiddleware) - r.Get("/interceptions", api.aiBridgeListInterceptions) - }) + api.AGPL.ExperimentalHandler.Group(func(r chi.Router) { + // Deprecated. + // TODO: remove with Beta release. + r.Route("/aibridge", aibridgeHandler(api, apiKeyMiddleware)) + }) - // This is a bit funky but since aibridge only exposes a HTTP - // handler, this is how it has to be. - r.HandleFunc("/*", func(rw http.ResponseWriter, r *http.Request) { - if api.aibridgedHandler == nil { - httpapi.Write(r.Context(), rw, http.StatusNotFound, codersdk.Response{ - Message: "aibridged handler not mounted", - }) - return - } - http.StripPrefix("/api/v2/aibridge", api.aibridgedHandler).ServeHTTP(rw, r) - }) - }) + api.AGPL.APIHandler.Group(func(r chi.Router) { + r.Route("/aibridge", aibridgeHandler(api, apiKeyMiddleware)) }) api.AGPL.APIHandler.Group(func(r chi.Router) { From b90c74a94d0c271a5d73ace01281941dac2acd4f Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:14:25 +1100 Subject: [PATCH 026/255] chore(scaletest): avoid polling workspace builds during workspace-updates tests (#20534) This PR is just committing the changes I made while running the `workspace-updates` load generator. It ensures we're not polling the workspace build progress in the background (while we also watch for workspace updates via the tailnet), and also removes an unnecessary query to `/api/v2/workspace/{id}` after each workspace is built. --- scaletest/createworkspaces/run.go | 6 +++++- scaletest/workspacebuild/run.go | 26 ++++++++++++++------------ scaletest/workspaceupdates/run.go | 4 ++++ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/scaletest/createworkspaces/run.go b/scaletest/createworkspaces/run.go index 49fe0548b38e5..09903c06cfab2 100644 --- a/scaletest/createworkspaces/run.go +++ b/scaletest/createworkspaces/run.go @@ -87,10 +87,14 @@ func (r *Runner) Run(ctx context.Context, id string, logs io.Writer) error { workspaceBuildConfig.OrganizationID = r.cfg.User.OrganizationID workspaceBuildConfig.UserID = user.ID.String() r.workspacebuildRunner = workspacebuild.NewRunner(client, workspaceBuildConfig) - workspace, err := r.workspacebuildRunner.RunReturningWorkspace(ctx, id, logs) + slimWorkspace, err := r.workspacebuildRunner.RunReturningWorkspace(ctx, id, logs) if err != nil { return xerrors.Errorf("create workspace: %w", err) } + workspace, err := client.Workspace(ctx, slimWorkspace.ID) + if err != nil { + return xerrors.Errorf("get full workspace info: %w", err) + } if r.cfg.Workspace.NoWaitForAgents { return nil diff --git a/scaletest/workspacebuild/run.go b/scaletest/workspacebuild/run.go index 308c18f0b6a03..f05173c6886bc 100644 --- a/scaletest/workspacebuild/run.go +++ b/scaletest/workspacebuild/run.go @@ -33,8 +33,13 @@ func NewRunner(client *codersdk.Client, cfg Config) *Runner { } } +type SlimWorkspace struct { + ID uuid.UUID + Name string +} + // Run implements Runnable. -func (r *Runner) RunReturningWorkspace(ctx context.Context, id string, logs io.Writer) (codersdk.Workspace, error) { +func (r *Runner) RunReturningWorkspace(ctx context.Context, id string, logs io.Writer) (SlimWorkspace, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -47,14 +52,14 @@ func (r *Runner) RunReturningWorkspace(ctx context.Context, id string, logs io.W if req.Name == "" { randName, err := loadtestutil.GenerateWorkspaceName(id) if err != nil { - return codersdk.Workspace{}, xerrors.Errorf("generate random name for workspace: %w", err) + return SlimWorkspace{}, xerrors.Errorf("generate random name for workspace: %w", err) } req.Name = randName } workspace, err := r.client.CreateWorkspace(ctx, r.cfg.OrganizationID, r.cfg.UserID, req) if err != nil { - return codersdk.Workspace{}, xerrors.Errorf("create workspace: %w", err) + return SlimWorkspace{}, xerrors.Errorf("create workspace: %w", err) } r.workspaceID = workspace.ID @@ -72,7 +77,7 @@ func (r *Runner) RunReturningWorkspace(ctx context.Context, id string, logs io.W TemplateVersionID: req.TemplateVersionID, }) if err != nil { - return codersdk.Workspace{}, xerrors.Errorf("create workspace build: %w", err) + return SlimWorkspace{}, xerrors.Errorf("create workspace build: %w", err) } err = waitForBuild(ctx, logs, r.client, workspace.LatestBuild.ID) if err == nil { @@ -80,7 +85,7 @@ func (r *Runner) RunReturningWorkspace(ctx context.Context, id string, logs io.W } } if err != nil { - return codersdk.Workspace{}, xerrors.Errorf("wait for build: %w", err) + return SlimWorkspace{}, xerrors.Errorf("wait for build: %w", err) } } } @@ -91,16 +96,13 @@ func (r *Runner) RunReturningWorkspace(ctx context.Context, id string, logs io.W _, _ = fmt.Fprintln(logs, "") err = waitForAgents(ctx, logs, r.client, workspace.ID) if err != nil { - return codersdk.Workspace{}, xerrors.Errorf("wait for agent: %w", err) + return SlimWorkspace{}, xerrors.Errorf("wait for agent: %w", err) } } - workspace, err = r.client.Workspace(ctx, workspace.ID) - if err != nil { - return codersdk.Workspace{}, xerrors.Errorf("get workspace %q: %w", workspace.ID.String(), err) - } - - return workspace, nil + // Some users of this runner might not need the full workspace, and + // want to avoid querying the workspace. + return SlimWorkspace{ID: workspace.ID, Name: workspace.Name}, nil } // CleanupRunner is a runner that deletes a workspace in the Run phase. diff --git a/scaletest/workspaceupdates/run.go b/scaletest/workspaceupdates/run.go index 4addf2b5a5939..fa05d290f0e54 100644 --- a/scaletest/workspaceupdates/run.go +++ b/scaletest/workspaceupdates/run.go @@ -116,6 +116,10 @@ func (r *Runner) Run(ctx context.Context, id string, logs io.Writer) error { workspaceBuildConfig.OrganizationID = r.cfg.User.OrganizationID workspaceBuildConfig.UserID = newUser.ID.String() workspaceBuildConfig.Request.Name = workspaceName + // We'll watch for completion ourselves via the tailnet workspace + // updates stream. + workspaceBuildConfig.NoWaitForAgents = true + workspaceBuildConfig.NoWaitForBuild = true runner := workspacebuild.NewRunner(newUserClient, workspaceBuildConfig) r.workspacebuildRunners = append(r.workspacebuildRunners, runner) From dc277618eeb762986949f423b36f94844aa695f7 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Thu, 30 Oct 2025 11:10:24 +0400 Subject: [PATCH 027/255] chore: refactor flags that target workspaces in scaletest (#20537) For the https://github.com/coder/internal/issues/913 we are going to be targeting running workspaces. So this PR modularizes the CLI flags and logic that select those targets so we can reuse it. --- cli/exp_scaletest.go | 145 +++++++++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 59 deletions(-) diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index 559ffbebd165d..d70780e4c3e0c 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -384,6 +384,88 @@ func (s *scaletestPrometheusFlags) attach(opts *serpent.OptionSet) { ) } +// workspaceTargetFlags holds common flags for targeting specific workspaces in scale tests. +type workspaceTargetFlags struct { + template string + targetWorkspaces string + useHostLogin bool +} + +// attach adds the workspace target flags to the given options set. +func (f *workspaceTargetFlags) attach(opts *serpent.OptionSet) { + *opts = append(*opts, + serpent.Option{ + Flag: "template", + FlagShorthand: "t", + Env: "CODER_SCALETEST_TEMPLATE", + Description: "Name or ID of the template. Traffic generation will be limited to workspaces created from this template.", + Value: serpent.StringOf(&f.template), + }, + serpent.Option{ + Flag: "target-workspaces", + Env: "CODER_SCALETEST_TARGET_WORKSPACES", + Description: "Target a specific range of workspaces in the format [START]:[END] (exclusive). Example: 0:10 will target the 10 first alphabetically sorted workspaces (0-9).", + Value: serpent.StringOf(&f.targetWorkspaces), + }, + serpent.Option{ + Flag: "use-host-login", + Env: "CODER_SCALETEST_USE_HOST_LOGIN", + Default: "false", + Description: "Connect as the currently logged in user.", + Value: serpent.BoolOf(&f.useHostLogin), + }, + ) +} + +// getTargetedWorkspaces retrieves the workspaces based on the template filter and target range. warnWriter is where to +// write a warning message if any workspaces were skipped due to ownership mismatch. +func (f *workspaceTargetFlags) getTargetedWorkspaces(ctx context.Context, client *codersdk.Client, organizationIDs []uuid.UUID, warnWriter io.Writer) ([]codersdk.Workspace, error) { + // Validate template if provided + if f.template != "" { + _, err := parseTemplate(ctx, client, organizationIDs, f.template) + if err != nil { + return nil, xerrors.Errorf("parse template: %w", err) + } + } + + // Parse target range + targetStart, targetEnd, err := parseTargetRange("workspaces", f.targetWorkspaces) + if err != nil { + return nil, xerrors.Errorf("parse target workspaces: %w", err) + } + + // Determine owner based on useHostLogin + var owner string + if f.useHostLogin { + owner = codersdk.Me + } + + // Get workspaces + workspaces, numSkipped, err := getScaletestWorkspaces(ctx, client, owner, f.template) + if err != nil { + return nil, err + } + if numSkipped > 0 { + cliui.Warnf(warnWriter, "CODER_DISABLE_OWNER_WORKSPACE_ACCESS is set on the deployment.\n\t%d workspace(s) were skipped due to ownership mismatch.\n\tSet --use-host-login to only target workspaces you own.", numSkipped) + } + + // Adjust targetEnd if not specified + if targetEnd == 0 { + targetEnd = len(workspaces) + } + + // Validate range + if len(workspaces) == 0 { + return nil, xerrors.Errorf("no scaletest workspaces exist") + } + if targetEnd > len(workspaces) { + return nil, xerrors.Errorf("target workspace end %d is greater than the number of workspaces %d", targetEnd, len(workspaces)) + } + + // Return the sliced workspaces + return workspaces[targetStart:targetEnd], nil +} + func requireAdmin(ctx context.Context, client *codersdk.Client) (codersdk.User, error) { me, err := client.User(ctx, codersdk.Me) if err != nil { @@ -1193,12 +1275,10 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { bytesPerTick int64 ssh bool disableDirect bool - useHostLogin bool app string - template string - targetWorkspaces string workspaceProxyURL string + targetFlags = &workspaceTargetFlags{} tracingFlags = &scaletestTracingFlags{} strategy = &scaletestStrategyFlags{} cleanupStrategy = newScaletestCleanupStrategy() @@ -1243,15 +1323,9 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { }, } - if template != "" { - _, err := parseTemplate(ctx, client, me.OrganizationIDs, template) - if err != nil { - return xerrors.Errorf("parse template: %w", err) - } - } - targetWorkspaceStart, targetWorkspaceEnd, err := parseTargetRange("workspaces", targetWorkspaces) + workspaces, err := targetFlags.getTargetedWorkspaces(ctx, client, me.OrganizationIDs, inv.Stdout) if err != nil { - return xerrors.Errorf("parse target workspaces: %w", err) + return err } appHost, err := client.AppHost(ctx) @@ -1259,30 +1333,6 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { return xerrors.Errorf("get app host: %w", err) } - var owner string - if useHostLogin { - owner = codersdk.Me - } - - workspaces, numSkipped, err := getScaletestWorkspaces(inv.Context(), client, owner, template) - if err != nil { - return err - } - if numSkipped > 0 { - cliui.Warnf(inv.Stdout, "CODER_DISABLE_OWNER_WORKSPACE_ACCESS is set on the deployment.\n\t%d workspace(s) were skipped due to ownership mismatch.\n\tSet --use-host-login to only target workspaces you own.", numSkipped) - } - - if targetWorkspaceEnd == 0 { - targetWorkspaceEnd = len(workspaces) - } - - if len(workspaces) == 0 { - return xerrors.Errorf("no scaletest workspaces exist") - } - if targetWorkspaceEnd > len(workspaces) { - return xerrors.Errorf("target workspace end %d is greater than the number of workspaces %d", targetWorkspaceEnd, len(workspaces)) - } - tracerProvider, closeTracing, tracingEnabled, err := tracingFlags.provider(ctx) if err != nil { return xerrors.Errorf("create tracer provider: %w", err) @@ -1307,10 +1357,6 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { th := harness.NewTestHarness(strategy.toStrategy(), cleanupStrategy.toStrategy()) for idx, ws := range workspaces { - if idx < targetWorkspaceStart || idx >= targetWorkspaceEnd { - continue - } - var ( agent codersdk.WorkspaceAgent name = "workspace-traffic" @@ -1415,19 +1461,6 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { } cmd.Options = []serpent.Option{ - { - Flag: "template", - FlagShorthand: "t", - Env: "CODER_SCALETEST_TEMPLATE", - Description: "Name or ID of the template. Traffic generation will be limited to workspaces created from this template.", - Value: serpent.StringOf(&template), - }, - { - Flag: "target-workspaces", - Env: "CODER_SCALETEST_TARGET_WORKSPACES", - Description: "Target a specific range of workspaces in the format [START]:[END] (exclusive). Example: 0:10 will target the 10 first alphabetically sorted workspaces (0-9).", - Value: serpent.StringOf(&targetWorkspaces), - }, { Flag: "bytes-per-tick", Env: "CODER_SCALETEST_WORKSPACE_TRAFFIC_BYTES_PER_TICK", @@ -1463,13 +1496,6 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { Description: "Send WebSocket traffic to a workspace app (proxied via coderd), cannot be used with --ssh.", Value: serpent.StringOf(&app), }, - { - Flag: "use-host-login", - Env: "CODER_SCALETEST_USE_HOST_LOGIN", - Default: "false", - Description: "Connect as the currently logged in user.", - Value: serpent.BoolOf(&useHostLogin), - }, { Flag: "workspace-proxy-url", Env: "CODER_SCALETEST_WORKSPACE_PROXY_URL", @@ -1479,6 +1505,7 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { }, } + targetFlags.attach(&cmd.Options) tracingFlags.attach(&cmd.Options) strategy.attach(&cmd.Options) cleanupStrategy.attach(&cmd.Options) From bc0c4ebaa7ea8c048ab98fba333f7343ce75055d Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:09:08 +1100 Subject: [PATCH 028/255] chore(dogfood): add back coder envbuilder template (#20576) I've given the CI dev.coder user Admin on the template, and tested the template still builds a workspace. --- dogfood/main.tf | 98 +++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/dogfood/main.tf b/dogfood/main.tf index 6fcfa90739eae..49bc3a611b2eb 100644 --- a/dogfood/main.tf +++ b/dogfood/main.tf @@ -9,6 +9,11 @@ terraform { } } +import { + to = coderd_template.envbuilder_dogfood + id = "e75f1212-834c-4183-8bed-d6817cac60a5" +} + data "coderd_organization" "default" { is_default = true } @@ -87,50 +92,49 @@ resource "coderd_template" "dogfood" { time_til_dormant_ms = 8640000000 } -# Disabled for now until we restore the deleted template. -# My bad - Dean -#resource "coderd_template" "envbuilder_dogfood" { -# name = "coder-envbuilder" -# display_name = "Write Coder on Coder using Envbuilder" -# description = "Write Coder on Coder using a workspace built by Envbuilder." -# icon = "/emojis/1f3d7.png" # 🏗️ -# organization_id = data.coderd_organization.default.id -# versions = [ -# { -# name = var.CODER_TEMPLATE_VERSION -# message = var.CODER_TEMPLATE_MESSAGE -# directory = "./coder-envbuilder" -# active = true -# tf_vars = [{ -# # clusters/dogfood-v2/coder/provisioner/configs/values.yaml#L191-L194 -# name = "envbuilder_cache_dockerconfigjson_path" -# value = "/home/coder/envbuilder-cache-dockerconfig.json" -# }] -# } -# ] -# acl = { -# groups = [{ -# id = data.coderd_organization.default.id -# role = "use" -# }] -# users = [{ -# id = data.coderd_user.machine.id -# role = "admin" -# }] -# } -# activity_bump_ms = 10800000 -# allow_user_auto_start = true -# allow_user_auto_stop = true -# allow_user_cancel_workspace_jobs = false -# auto_start_permitted_days_of_week = ["friday", "monday", "saturday", "sunday", "thursday", "tuesday", "wednesday"] -# auto_stop_requirement = { -# days_of_week = ["sunday"] -# weeks = 1 -# } -# default_ttl_ms = 28800000 -# deprecation_message = null -# failure_ttl_ms = 604800000 -# require_active_version = true -# time_til_dormant_autodelete_ms = 7776000000 -# time_til_dormant_ms = 8640000000 -#} + +resource "coderd_template" "envbuilder_dogfood" { + name = "coder-envbuilder" + display_name = "Write Coder on Coder using Envbuilder" + description = "Write Coder on Coder using a workspace built by Envbuilder." + icon = "/emojis/1f3d7.png" # 🏗️ + organization_id = data.coderd_organization.default.id + versions = [ + { + name = var.CODER_TEMPLATE_VERSION + message = var.CODER_TEMPLATE_MESSAGE + directory = "./coder-envbuilder" + active = true + tf_vars = [{ + # clusters/dogfood-v2/coder/provisioner/configs/values.yaml#L191-L194 + name = "envbuilder_cache_dockerconfigjson_path" + value = "/home/coder/envbuilder-cache-dockerconfig.json" + }] + } + ] + acl = { + groups = [{ + id = data.coderd_organization.default.id + role = "use" + }] + users = [{ + id = data.coderd_user.machine.id + role = "admin" + }] + } + activity_bump_ms = 10800000 + allow_user_auto_start = true + allow_user_auto_stop = true + allow_user_cancel_workspace_jobs = false + auto_start_permitted_days_of_week = ["friday", "monday", "saturday", "sunday", "thursday", "tuesday", "wednesday"] + auto_stop_requirement = { + days_of_week = ["sunday"] + weeks = 1 + } + default_ttl_ms = 28800000 + deprecation_message = null + failure_ttl_ms = 604800000 + require_active_version = true + time_til_dormant_autodelete_ms = 7776000000 + time_til_dormant_ms = 8640000000 +} From 94f6e83cfa064a2a1f49561c46cba350fa25af27 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Thu, 30 Oct 2025 12:45:47 +0400 Subject: [PATCH 029/255] docs: fix typo: worklods (#20578) fixes typo. --- docs/admin/templates/managing-templates/external-workspaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/admin/templates/managing-templates/external-workspaces.md b/docs/admin/templates/managing-templates/external-workspaces.md index 25a97db468867..5d547b67fc891 100644 --- a/docs/admin/templates/managing-templates/external-workspaces.md +++ b/docs/admin/templates/managing-templates/external-workspaces.md @@ -20,7 +20,7 @@ External workspaces offer flexibility and control in complex environments: - **Incremental adoption of Coder** - Integrate with existing infrastructure gradually without needing to migrate everything at once. This is particularly useful when gradually migrating worklods to Coder without refactoring current infrastructure. + Integrate with existing infrastructure gradually without needing to migrate everything at once. This is particularly useful when gradually migrating workloads to Coder without refactoring current infrastructure. - **Flexibility** From 73dedcc76568436c9c5c6091b849e1093e4f2f85 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 30 Oct 2025 10:37:51 +0000 Subject: [PATCH 030/255] fix: delete related task when deleting workspace (#20567) * Instead of prompting the user to start a deleted workspace (which is silly), prompt them to create a new task instead. * Adds a warning dialog when deleting a workspace * Updates provisionerdserver to delete the related task if a workspace is related to a task --- coderd/aitasks_test.go | 52 ++++++++++++++++--- coderd/database/dbauthz/dbauthz.go | 4 +- .../provisionerdserver/provisionerdserver.go | 8 +++ .../WorkspaceDeleteDialog.stories.tsx | 12 ++++- .../WorkspaceDeleteDialog.tsx | 22 +++++++- site/src/pages/TaskPage/TaskPage.stories.tsx | 10 ++++ site/src/pages/TaskPage/TaskPage.tsx | 22 +++++++- 7 files changed, 118 insertions(+), 12 deletions(-) diff --git a/coderd/aitasks_test.go b/coderd/aitasks_test.go index 9fa8e168a4ecb..4f645d34cf3bd 100644 --- a/coderd/aitasks_test.go +++ b/coderd/aitasks_test.go @@ -355,10 +355,10 @@ func TestTasks(t *testing.T) { } }) - t.Run("NoWorkspace", func(t *testing.T) { + t.Run("DeletedWorkspace", func(t *testing.T) { t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) user := coderdtest.CreateFirstUser(t, client) template := createAITemplate(t, client, user) ctx := testutil.Context(t, testutil.WaitLong) @@ -372,14 +372,54 @@ func TestTasks(t *testing.T) { ws, err := client.Workspace(ctx, task.WorkspaceID.UUID) require.NoError(t, err) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) - // Delete the task workspace - coderdtest.MustTransitionWorkspace(t, client, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionDelete) - // We should still be able to fetch the task after deleting its workspace + + // Mark the workspace as deleted directly in the database, bypassing provisionerd. + require.NoError(t, db.UpdateWorkspaceDeletedByID(dbauthz.AsProvisionerd(ctx), database.UpdateWorkspaceDeletedByIDParams{ + ID: ws.ID, + Deleted: true, + })) + // We should still be able to fetch the task if its workspace was deleted. + // Provisionerdserver will attempt delete the related task when deleting a workspace. + // This test ensures that we can still handle the case where, for some reason, the + // task has not been marked as deleted, but the workspace has. task, err = exp.TaskByID(ctx, task.ID) - require.NoError(t, err, "fetching a task should still work after deleting its related workspace") + require.NoError(t, err, "fetching a task should still work if its related workspace is deleted") err = exp.DeleteTask(ctx, task.OwnerID.String(), task.ID) require.NoError(t, err, "should be possible to delete a task with no workspace") }) + + t.Run("DeletingTaskWorkspaceDeletesTask", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + template := createAITemplate(t, client, user) + + ctx := testutil.Context(t, testutil.WaitLong) + + exp := codersdk.NewExperimentalClient(client) + task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{ + TemplateVersionID: template.ActiveVersionID, + Input: "delete me", + }) + require.NoError(t, err) + require.True(t, task.WorkspaceID.Valid, "task should have a workspace ID") + ws, err := client.Workspace(ctx, task.WorkspaceID.UUID) + require.NoError(t, err) + if assert.True(t, ws.TaskID.Valid, "task id should be set on workspace") { + assert.Equal(t, task.ID, ws.TaskID.UUID, "workspace task id should match") + } + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID) + + // When; the task workspace is deleted + coderdtest.MustTransitionWorkspace(t, client, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionDelete) + // Then: the task associated with the workspace is also deleted + _, err = exp.TaskByID(ctx, task.ID) + require.Error(t, err, "expected an error fetching the task") + var sdkErr *codersdk.Error + require.ErrorAs(t, err, &sdkErr, "expected a codersdk.Error") + require.Equal(t, http.StatusNotFound, sdkErr.StatusCode()) + }) }) t.Run("Send", func(t *testing.T) { diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 1b2a6a5d97590..8066ebd0479a1 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -219,8 +219,8 @@ var ( rbac.ResourceUser.Type: {policy.ActionRead, policy.ActionReadPersonal, policy.ActionUpdatePersonal}, rbac.ResourceWorkspaceDormant.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStop}, rbac.ResourceWorkspace.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop, policy.ActionCreateAgent}, - // Provisionerd needs to read and update tasks associated with workspaces. - rbac.ResourceTask.Type: {policy.ActionRead, policy.ActionUpdate}, + // Provisionerd needs to read, update, and delete tasks associated with workspaces. + rbac.ResourceTask.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}, rbac.ResourceApiKey.Type: {policy.WildcardSymbol}, // When org scoped provisioner credentials are implemented, // this can be reduced to read a specific org. diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index bf7741bdc260f..2e00796d1cd64 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -2278,6 +2278,14 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro if err != nil { return xerrors.Errorf("update workspace deleted: %w", err) } + if workspace.TaskID.Valid { + if _, err := db.DeleteTask(ctx, database.DeleteTaskParams{ + ID: workspace.TaskID.UUID, + DeletedAt: dbtime.Now(), + }); err != nil && !errors.Is(err, sql.ErrNoRows) { + return xerrors.Errorf("delete task related to workspace: %w", err) + } + } return nil }, nil) diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceDeleteDialog.stories.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceDeleteDialog.stories.tsx index b5fcd44b7c9c8..7debfb1ce9d8c 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceDeleteDialog.stories.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceDeleteDialog.stories.tsx @@ -1,4 +1,8 @@ -import { MockFailedWorkspace, MockWorkspace } from "testHelpers/entities"; +import { + MockFailedWorkspace, + MockTaskWorkspace, + MockWorkspace, +} from "testHelpers/entities"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { daysAgo } from "utils/time"; import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog"; @@ -45,3 +49,9 @@ export const UnhealthyAdminView: Story = { canDeleteFailedWorkspace: true, }, }; + +export const WithTask: Story = { + args: { + workspace: MockTaskWorkspace, + }, +}; diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceDeleteDialog.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceDeleteDialog.tsx index 2cfb74f2765c3..245f95c0f74d8 100644 --- a/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceDeleteDialog.tsx +++ b/site/src/modules/workspaces/WorkspaceMoreActions/WorkspaceDeleteDialog.tsx @@ -56,6 +56,8 @@ export const WorkspaceDeleteDialog: FC = ({ (workspace.latest_build.status === "failed" || workspace.latest_build.status === "canceled"); + const hasTask = !!workspace.task_id; + return ( = ({ "data-testid": "delete-dialog-name-confirmation", }} /> + {hasTask && ( +
+
+

This workspace is related to a task

+ + Deleting this workspace will also delete{" "} + + this task + + . + +
+
+ )} {canOrphan && ( -
+
({ + warnContainer: (theme) => ({ marginTop: 24, display: "flex", backgroundColor: theme.roles.danger.background, diff --git a/site/src/pages/TaskPage/TaskPage.stories.tsx b/site/src/pages/TaskPage/TaskPage.stories.tsx index 19003eb621699..22fccb7349863 100644 --- a/site/src/pages/TaskPage/TaskPage.stories.tsx +++ b/site/src/pages/TaskPage/TaskPage.stories.tsx @@ -1,4 +1,5 @@ import { + MockDeletedWorkspace, MockFailedWorkspace, MockStartingWorkspace, MockStoppedWorkspace, @@ -169,6 +170,15 @@ export const TerminatedBuildWithStatus: Story = { }, }; +export const DeletedWorkspace: Story = { + beforeEach: () => { + spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); + spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue( + MockDeletedWorkspace, + ); + }, +}; + export const WaitingStartupScripts: Story = { beforeEach: () => { spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); diff --git a/site/src/pages/TaskPage/TaskPage.tsx b/site/src/pages/TaskPage/TaskPage.tsx index 17cdc8b6861d7..49f9d7569680b 100644 --- a/site/src/pages/TaskPage/TaskPage.tsx +++ b/site/src/pages/TaskPage/TaskPage.tsx @@ -221,7 +221,27 @@ const WorkspaceNotRunning: FC = ({ workspace }) => { ? mutateStartWorkspace.error : undefined; - return ( + const deleted = workspace.latest_build?.transition === ("delete" as const); + + return deleted ? ( + +
+
+

+ Task workspace was deleted. +

+ + This task cannot be resumed. Delete this task and create a new one. + + +
+
+
+ ) : (
From 2bcf08457b710053e4612426ff3393ef7daa6d87 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 30 Oct 2025 10:56:24 +0000 Subject: [PATCH 031/255] ci: revert workaround for get.helm.sh outage (#20552) (#20557) Reverts the temporary workaround in #20552. Merge after get.helm.sh is once again operational. --- .github/workflows/ci.yaml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5efa2edbc9fb0..96d0ce23953cf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -204,17 +204,9 @@ jobs: # Needed for helm chart linting - name: Install helm - # uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 - # with: - # version: v3.9.2 - # The below is taken from https://helm.sh/docs/intro/install/#from-apt-debianubuntu - run: | - set -euo pipefail - sudo apt-get install curl gpg apt-transport-https --yes - curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null - echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list - sudo apt-get update - sudo apt-get install helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 + with: + version: v3.9.2 - name: make lint run: | From 984a834e81a701aea75e4719c8c848cb9ba8d7e0 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Thu, 30 Oct 2025 16:17:04 +0400 Subject: [PATCH 032/255] docs: revert work in progress 10k scale doc (#20580) Reverts in-progress 10k docs because people found it confusing. --- .../validated-architectures/10k-users.md | 124 ------------------ .../validated-architectures/index.md | 2 - docs/manifest.json | 5 - 3 files changed, 131 deletions(-) delete mode 100644 docs/admin/infrastructure/validated-architectures/10k-users.md diff --git a/docs/admin/infrastructure/validated-architectures/10k-users.md b/docs/admin/infrastructure/validated-architectures/10k-users.md deleted file mode 100644 index 486ac8192c991..0000000000000 --- a/docs/admin/infrastructure/validated-architectures/10k-users.md +++ /dev/null @@ -1,124 +0,0 @@ -# Reference Architecture: up to 10,000 users - -> [!CAUTION] -> This page is a work in progress. -> -> We are actively testing different load profiles for this user target and will be updating -> recommendations. Use these recommendations as a starting point, but monitor your cluster resource -> utilization and adjust. - -The 10,000 users architecture targets large-scale enterprises with development -teams in multiple geographic regions. - -**Geographic Distribution**: For these tests we deploy on 3 cloud-managed Kubernetes clusters in -the following regions: - -1. USA - Primary - Coderd collocated with the PostgreSQL database deployment. -2. Europe - Workspace Proxies -3. Asia - Workspace Proxies - -**High Availability**: Typically, such scale requires a fully-managed HA -PostgreSQL service, and all Coder observability features enabled for operational -purposes. - -**Observability**: Deploy monitoring solutions to gather Prometheus metrics and -visualize them with Grafana to gain detailed insights into infrastructure and -application behavior. This allows operators to respond quickly to incidents and -continuously improve the reliability and performance of the platform. - -## Testing Methodology - -### Workspace Network Traffic - -6000 concurrent workspaces (2000 per region), each sending 10 kB/s application traffic. - -Test procedure: - -1. Create workspaces. This happens simultaneously in each region with 200 provisioners (and thus 600 concurrent builds). -2. Wait 5 minutes to establish baselines for metrics. -3. Generate 10 kB/s traffic to each workspace (originating within the same region & cluster). - -After, we examine the Coderd, Workspace Proxy, and Database metrics to look for issues. - -### Dynamic Parameters - -1000 connections simulating changing parameters while configuring a new workspace. - -Test procedure: - -1. Create a template with complex parameter logic and multiple template versions. -1. Partition the connections among the template versions (forces Coder to process multiple template files) -1. Simultaneously connect to the dynamic-parameters API websocket endpoint for the template version -1. Wait for the initial parameter update. -1. Send a new parameter value that has cascading effects among other parameters. -1. Wait for the next update. - -After, we examine the latency in the initial connection and update, as well as Coderd and Database metrics to look for -issues. - -### API Request Traffic - -To be determined. - -## Hardware recommendations - -### Coderd - -These are deployed in the Primary region only. - -| vCPU Limit | Memory Limit | Replicas | GCP Node Pool Machine Type | -|----------------|--------------|----------|----------------------------| -| 4 vCPU (4000m) | 12 GiB | 10 | `c2d-standard-16` | - -### Provisioners - -These are deployed in each of the 3 regions. - -| vCPU Limit | Memory Limit | Replicas | GCP Node Pool Machine Type | -|-----------------|--------------|----------|----------------------------| -| 0.1 vCPU (100m) | 1 GiB | 200 | `c2d-standard-16` | - -**Footnotes**: - -- Each provisioner handles a single concurrent build, so this configuration implies 200 concurrent - workspace builds per region. -- Provisioners are run as a separate Kubernetes Deployment from Coderd, although they may - share the same node pool. -- Separate provisioners into different namespaces in favor of zero-trust or - multi-cloud deployments. - -### Workspace Proxies - -These are deployed in the non-Primary regions only. - -| vCPU Limit | Memory Limit | Replicas | GCP Node Pool Machine Type | -|----------------|--------------|----------|----------------------------| -| 4 vCPU (4000m) | 12 GiB | 10 | `c2d-standard-16` | - -**Footnotes**: - -- Our testing implies this is somewhat overspecced for the loads we have tried. We are in process of revising these numbers. - -### Workspaces - -These numbers are for each of the 3 regions. We recommend that you use a separate node pool for user Workspaces. - -| Users | Node capacity | Replicas | GCP | AWS | Azure | -|-------------|----------------------|-------------------------------|------------------|--------------|-------------------| -| Up to 3,000 | 8 vCPU, 32 GB memory | 256 nodes, 12 workspaces each | `t2d-standard-8` | `m5.2xlarge` | `Standard_D8s_v3` | - -**Footnotes**: - -- Assumed that a workspace user needs 2 GB memory to perform -- Maximum number of Kubernetes workspace pods per node: 256 -- As workspace nodes can be distributed between regions, on-premises networks - and cloud areas, consider different namespaces in favor of zero-trust or - multi-cloud deployments. - -### Database nodes - -We conducted our test using the `db-custom-16-61440` tier on Google Cloud SQL. - -**Footnotes**: - -- This database tier was only just able to keep up with 600 concurrent builds in our tests. diff --git a/docs/admin/infrastructure/validated-architectures/index.md b/docs/admin/infrastructure/validated-architectures/index.md index 59602f22bc47a..6bd18f7f3c132 100644 --- a/docs/admin/infrastructure/validated-architectures/index.md +++ b/docs/admin/infrastructure/validated-architectures/index.md @@ -220,8 +220,6 @@ For sizing recommendations, see the below reference architectures: - [Up to 3,000 users](3k-users.md) -- DRAFT: [Up to 10,000 users](10k-users.md) - ### AWS Instance Types For production AWS deployments, we recommend using non-burstable instance types, diff --git a/docs/manifest.json b/docs/manifest.json index 57711406c87d7..8ef8e3e5fa326 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -396,11 +396,6 @@ "title": "Up to 3,000 Users", "description": "Enterprise-scale architecture recommendations for Coder deployments that support up to 3,000 users", "path": "./admin/infrastructure/validated-architectures/3k-users.md" - }, - { - "title": "Up to 10,000 Users", - "description": "Enterprise-scale architecture recommendations for Coder deployments that support up to 10,000 users", - "path": "./admin/infrastructure/validated-architectures/10k-users.md" } ] }, From 38017010cec19bb0fc5479a7debcc341a5a63a9d Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 30 Oct 2025 13:32:18 +0000 Subject: [PATCH 033/255] fix(coderd): disallow POSTing a workspace build on a deleted workspace (#20584) - Adds a check on /api/v2/workspacebuilds to disallow creating a START or STOP build if the workspace is deleted. - DELETEs are still allowed. --- coderd/workspacebuilds.go | 9 +++++ coderd/workspacebuilds_test.go | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 1e3020376041b..e4e3e497a1a22 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -335,6 +335,15 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { return } + // We want to allow a delete build for a deleted workspace, but not a start or stop build. + if workspace.Deleted && createBuild.Transition != codersdk.WorkspaceTransitionDelete { + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ + Message: fmt.Sprintf("Cannot %s a deleted workspace!", createBuild.Transition), + Detail: "This workspace has been deleted and cannot be modified.", + }) + return + } + apiBuild, err := api.postWorkspaceBuildsInternal( ctx, apiKey, diff --git a/coderd/workspacebuilds_test.go b/coderd/workspacebuilds_test.go index f857296db1a5c..d0ab64b1aeb32 100644 --- a/coderd/workspacebuilds_test.go +++ b/coderd/workspacebuilds_test.go @@ -1840,6 +1840,68 @@ func TestPostWorkspaceBuild(t *testing.T) { require.NoError(t, err) require.Equal(t, codersdk.BuildReasonDashboard, build.Reason) }) + t.Run("DeletedWorkspace", func(t *testing.T) { + t.Parallel() + + // Given: a workspace that has already been deleted + var ( + ctx = testutil.Context(t, testutil.WaitShort) + logger = slogtest.Make(t, &slogtest.Options{}).Leveled(slog.LevelError) + adminClient, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{ + Logger: &logger, + }) + admin = coderdtest.CreateFirstUser(t, adminClient) + workspaceOwnerClient, member1 = coderdtest.CreateAnotherUser(t, adminClient, admin.OrganizationID) + otherMemberClient, _ = coderdtest.CreateAnotherUser(t, adminClient, admin.OrganizationID) + ws = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{OwnerID: member1.ID, OrganizationID: admin.OrganizationID}). + Seed(database.WorkspaceBuild{Transition: database.WorkspaceTransitionDelete}). + Do() + ) + + // This needs to be done separately as provisionerd handles marking the workspace as deleted + // and we're skipping provisionerd here for speed. + require.NoError(t, db.UpdateWorkspaceDeletedByID(dbauthz.AsProvisionerd(ctx), database.UpdateWorkspaceDeletedByIDParams{ + ID: ws.Workspace.ID, + Deleted: true, + })) + + // Assert test invariant: Workspace should be deleted + dbWs, err := db.GetWorkspaceByID(dbauthz.AsProvisionerd(ctx), ws.Workspace.ID) + require.NoError(t, err) + require.True(t, dbWs.Deleted, "workspace should be deleted") + + for _, tc := range []struct { + user *codersdk.Client + tr codersdk.WorkspaceTransition + expectStatus int + }{ + // You should not be allowed to mess with a workspace you don't own, regardless of its deleted state. + {otherMemberClient, codersdk.WorkspaceTransitionStart, http.StatusNotFound}, + {otherMemberClient, codersdk.WorkspaceTransitionStop, http.StatusNotFound}, + {otherMemberClient, codersdk.WorkspaceTransitionDelete, http.StatusNotFound}, + // Starting or stopping a workspace is not allowed when it is deleted. + {workspaceOwnerClient, codersdk.WorkspaceTransitionStart, http.StatusConflict}, + {workspaceOwnerClient, codersdk.WorkspaceTransitionStop, http.StatusConflict}, + // We allow a delete just in case a retry is required. In most cases, this will be a no-op. + // Note: this is the last test case because it will change the state of the workspace. + {workspaceOwnerClient, codersdk.WorkspaceTransitionDelete, http.StatusOK}, + } { + // When: we create a workspace build with the given transition + _, err = tc.user.CreateWorkspaceBuild(ctx, ws.Workspace.ID, codersdk.CreateWorkspaceBuildRequest{ + Transition: tc.tr, + }) + + // Then: we allow ONLY a delete build for a deleted workspace. + if tc.expectStatus < http.StatusBadRequest { + require.NoError(t, err, "creating a %s build for a deleted workspace should not error", tc.tr) + } else { + var apiError *codersdk.Error + require.Error(t, err, "creating a %s build for a deleted workspace should return an error", tc.tr) + require.ErrorAs(t, err, &apiError) + require.Equal(t, tc.expectStatus, apiError.StatusCode()) + } + } + }) } func TestWorkspaceBuildTimings(t *testing.T) { From 197b422a31fff71e3e99adc6cd031af769a994a6 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 30 Oct 2025 14:08:06 +0000 Subject: [PATCH 034/255] chore: add `tzdata` to dockerfile base (#20553) When deploying Coder using the ghcr.io/coder/coder image, it is not possible to set the "timezone" field in a preset with an embedded provisioner. This is due to the container image not having the IANA time zone database installed, which causes an issue when the terraform provider attempts to validate the given timezone is valid. --- scripts/Dockerfile.base | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/Dockerfile.base b/scripts/Dockerfile.base index ca48243cdfe09..da27f98a57dcb 100644 --- a/scripts/Dockerfile.base +++ b/scripts/Dockerfile.base @@ -12,7 +12,8 @@ RUN apk add --no-cache \ bash \ git \ openssl \ - openssh-client && \ + openssh-client \ + tzdata && \ addgroup \ -g 1000 \ coder && \ From d80b5fc8eda9b72e1b793fef627e83b8b36479da Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Thu, 30 Oct 2025 16:45:51 +0000 Subject: [PATCH 035/255] refactor!: remove TaskAppID from codersdk.WorkspaceBuild (#20583) Remove the `TaskAppID` field from `codersdk.WorkspaceBuild`. Consumers can instead use the new `codersdk.Task` data model for this information. --- coderd/apidoc/docs.go | 6 +- coderd/apidoc/swagger.json | 6 +- coderd/workspacebuilds.go | 1 - codersdk/workspacebuilds.go | 3 +- docs/reference/api/builds.md | 8 +- docs/reference/api/schemas.md | 6 +- docs/reference/api/workspaces.md | 6 - site/src/api/typesGenerated.ts | 3 +- site/src/pages/TaskPage/TaskApps.stories.tsx | 14 +- site/src/pages/TaskPage/TaskApps.tsx | 13 +- site/src/pages/TaskPage/TaskPage.stories.tsx | 196 +++++++++---------- site/src/pages/TaskPage/TaskPage.tsx | 4 +- 12 files changed, 121 insertions(+), 145 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 2fe716a0775f6..beacf64fc20b4 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -20530,7 +20530,7 @@ const docTemplate = `{ "type": "object", "properties": { "ai_task_sidebar_app_id": { - "description": "Deprecated: This field has been replaced with ` + "`" + `TaskAppID` + "`" + `", + "description": "Deprecated: This field has been replaced with ` + "`" + `Task.WorkspaceAppID` + "`" + `", "type": "string", "format": "uuid" }, @@ -20612,10 +20612,6 @@ const docTemplate = `{ } ] }, - "task_app_id": { - "type": "string", - "format": "uuid" - }, "template_version_id": { "type": "string", "format": "uuid" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 10031b2dd36be..477b01a8970aa 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -18864,7 +18864,7 @@ "type": "object", "properties": { "ai_task_sidebar_app_id": { - "description": "Deprecated: This field has been replaced with `TaskAppID`", + "description": "Deprecated: This field has been replaced with `Task.WorkspaceAppID`", "type": "string", "format": "uuid" }, @@ -18942,10 +18942,6 @@ } ] }, - "task_app_id": { - "type": "string", - "format": "uuid" - }, "template_version_id": { "type": "string", "format": "uuid" diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index e4e3e497a1a22..d064a0ef3f574 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -1228,7 +1228,6 @@ func (api *API) convertWorkspaceBuild( TemplateVersionPresetID: presetID, HasAITask: hasAITask, AITaskSidebarAppID: taskAppID, - TaskAppID: taskAppID, HasExternalAgent: hasExternalAgent, }, nil } diff --git a/codersdk/workspacebuilds.go b/codersdk/workspacebuilds.go index fee4c114b7eae..8c38bd5c6469b 100644 --- a/codersdk/workspacebuilds.go +++ b/codersdk/workspacebuilds.go @@ -89,9 +89,8 @@ type WorkspaceBuild struct { MatchedProvisioners *MatchedProvisioners `json:"matched_provisioners,omitempty"` TemplateVersionPresetID *uuid.UUID `json:"template_version_preset_id" format:"uuid"` HasAITask *bool `json:"has_ai_task,omitempty"` - // Deprecated: This field has been replaced with `TaskAppID` + // Deprecated: This field has been replaced with `Task.WorkspaceAppID` AITaskSidebarAppID *uuid.UUID `json:"ai_task_sidebar_app_id,omitempty" format:"uuid"` - TaskAppID *uuid.UUID `json:"task_app_id,omitempty" format:"uuid"` HasExternalAgent *bool `json:"has_external_agent,omitempty"` } diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md index ea207f84eab39..82b7cb8365a3e 100644 --- a/docs/reference/api/builds.md +++ b/docs/reference/api/builds.md @@ -222,7 +222,6 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -464,7 +463,6 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -1197,7 +1195,6 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -1512,7 +1509,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -1540,7 +1536,7 @@ Status Code **200** | Name | Type | Required | Restrictions | Description | |----------------------------------|--------------------------------------------------------------------------------------------------------|----------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `[array item]` | array | false | | | -| `» ai_task_sidebar_app_id` | string(uuid) | false | | Deprecated: This field has been replaced with `TaskAppID` | +| `» ai_task_sidebar_app_id` | string(uuid) | false | | Deprecated: This field has been replaced with `Task.WorkspaceAppID` | | `» build_number` | integer | false | | | | `» created_at` | string(date-time) | false | | | | `» daily_cost` | integer | false | | | @@ -1691,7 +1687,6 @@ Status Code **200** | `»» type` | string | false | | | | `»» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | | | `» status` | [codersdk.WorkspaceStatus](schemas.md#codersdkworkspacestatus) | false | | | -| `» task_app_id` | string(uuid) | false | | | | `» template_version_id` | string(uuid) | false | | | | `» template_version_name` | string | false | | | | `» template_version_preset_id` | string(uuid) | false | | | @@ -2013,7 +2008,6 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 300b79b6f435a..351afae7aa8c0 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -10166,7 +10166,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -11341,7 +11340,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -11359,7 +11357,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| | Name | Type | Required | Restrictions | Description | |------------------------------|-------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------| -| `ai_task_sidebar_app_id` | string | false | | Deprecated: This field has been replaced with `TaskAppID` | +| `ai_task_sidebar_app_id` | string | false | | Deprecated: This field has been replaced with `Task.WorkspaceAppID` | | `build_number` | integer | false | | | | `created_at` | string | false | | | | `daily_cost` | integer | false | | | @@ -11375,7 +11373,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | | | `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | | | `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | | -| `task_app_id` | string | false | | | | `template_version_id` | string | false | | | | `template_version_name` | string | false | | | | `template_version_preset_id` | string | false | | | @@ -12165,7 +12162,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", diff --git a/docs/reference/api/workspaces.md b/docs/reference/api/workspaces.md index 3e52d9e0a2d60..4bd188df3daf5 100644 --- a/docs/reference/api/workspaces.md +++ b/docs/reference/api/workspaces.md @@ -277,7 +277,6 @@ of the template will be used. } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -573,7 +572,6 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -894,7 +892,6 @@ of the template will be used. } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -1176,7 +1173,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -1473,7 +1469,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", @@ -2029,7 +2024,6 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \ } ], "status": "pending", - "task_app_id": "ca438251-3e16-4fae-b9ab-dd3c237c3735", "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", "template_version_name": "string", "template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 1c5d101e2676d..b6e17cf467320 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -6394,10 +6394,9 @@ export interface WorkspaceBuild { readonly template_version_preset_id: string | null; readonly has_ai_task?: boolean; /** - * Deprecated: This field has been replaced with `TaskAppID` + * Deprecated: This field has been replaced with `Task.WorkspaceAppID` */ readonly ai_task_sidebar_app_id?: string; - readonly task_app_id?: string; readonly has_external_agent?: boolean; } diff --git a/site/src/pages/TaskPage/TaskApps.stories.tsx b/site/src/pages/TaskPage/TaskApps.stories.tsx index 752c52cfde423..dcce3f5949d21 100644 --- a/site/src/pages/TaskPage/TaskApps.stories.tsx +++ b/site/src/pages/TaskPage/TaskApps.stories.tsx @@ -1,5 +1,6 @@ import { MockPrimaryWorkspaceProxy, + MockTask, MockUserOwner, MockWorkspace, MockWorkspaceAgent, @@ -8,7 +9,7 @@ import { } from "testHelpers/entities"; import { withAuthProvider, withProxyProvider } from "testHelpers/storybook"; import type { Meta, StoryObj } from "@storybook/react-vite"; -import type { Workspace, WorkspaceApp } from "api/typesGenerated"; +import type { Task, Workspace, WorkspaceApp } from "api/typesGenerated"; import { getPreferredProxy } from "contexts/ProxyContext"; import kebabCase from "lodash/kebabCase"; import { TaskApps } from "./TaskApps"; @@ -19,6 +20,11 @@ const mockExternalApp: WorkspaceApp = { health: "healthy", }; +const mockTask: Task = { + ...MockTask, + workspace_app_id: null, +}; + const meta: Meta = { title: "pages/TaskPage/TaskApps", component: TaskApps, @@ -33,24 +39,28 @@ type Story = StoryObj; export const NoEmbeddedApps: Story = { args: { + task: mockTask, workspace: mockWorkspaceWithApps([]), }, }; export const WithExternalAppsOnly: Story = { args: { + task: mockTask, workspace: mockWorkspaceWithApps([mockExternalApp]), }, }; export const WithEmbeddedApps: Story = { args: { + task: mockTask, workspace: mockWorkspaceWithApps([mockEmbeddedApp()]), }, }; export const WithMixedApps: Story = { args: { + task: mockTask, workspace: mockWorkspaceWithApps([mockEmbeddedApp(), mockExternalApp]), }, }; @@ -69,6 +79,7 @@ export const WithWildcardWarning: Story = { user: MockUserOwner, }, args: { + task: mockTask, workspace: mockWorkspaceWithApps([ { ...mockEmbeddedApp(), @@ -80,6 +91,7 @@ export const WithWildcardWarning: Story = { export const WithManyEmbeddedApps: Story = { args: { + task: mockTask, workspace: mockWorkspaceWithApps([ mockEmbeddedApp("Code Server"), mockEmbeddedApp("Jupyter Notebook"), diff --git a/site/src/pages/TaskPage/TaskApps.tsx b/site/src/pages/TaskPage/TaskApps.tsx index 2f05145bca46a..59ada2181119b 100644 --- a/site/src/pages/TaskPage/TaskApps.tsx +++ b/site/src/pages/TaskPage/TaskApps.tsx @@ -1,4 +1,4 @@ -import type { Workspace } from "api/typesGenerated"; +import type { Task, Workspace } from "api/typesGenerated"; import { Button } from "components/Button/Button"; import { DropdownMenu, @@ -24,18 +24,17 @@ import { docs } from "utils/docs"; import { TaskAppIFrame, TaskIframe } from "./TaskAppIframe"; type TaskAppsProps = { + task: Task; workspace: Workspace; }; const TERMINAL_TAB_ID = "terminal"; -export const TaskApps: FC = ({ workspace }) => { +export const TaskApps: FC = ({ task, workspace }) => { const apps = getAllAppsWithAgent(workspace).filter( // The Chat UI app will be displayed in the sidebar, so we don't want to // show it as a web app. - (app) => - app.id !== workspace.latest_build.task_app_id && - app.health !== "disabled", + (app) => app.id !== task.workspace_app_id && app.health !== "disabled", ); const [embeddedApps, externalApps] = splitEmbeddedAndExternalApps(apps); const [activeAppId, setActiveAppId] = useState(embeddedApps.at(0)?.id); @@ -43,8 +42,8 @@ export const TaskApps: FC = ({ workspace }) => { embeddedApps.length > 0 || externalApps.length > 0; const taskAgent = apps.at(0)?.agent; const terminalHref = getTerminalHref({ - username: workspace.owner_name, - workspace: workspace.name, + username: task.owner_name, + workspace: task.workspace_name, agent: taskAgent?.name, }); const isTerminalActive = activeAppId === TERMINAL_TAB_ID; diff --git a/site/src/pages/TaskPage/TaskPage.stories.tsx b/site/src/pages/TaskPage/TaskPage.stories.tsx index 22fccb7349863..b247a2a16b377 100644 --- a/site/src/pages/TaskPage/TaskPage.stories.tsx +++ b/site/src/pages/TaskPage/TaskPage.stories.tsx @@ -23,7 +23,7 @@ import { } from "testHelpers/storybook"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { API } from "api/api"; -import type { Workspace, WorkspaceApp } from "api/typesGenerated"; +import type { Task, Workspace, WorkspaceApp } from "api/typesGenerated"; import { expect, spyOn, userEvent, waitFor, within } from "storybook/test"; import { reactRouterParameters } from "storybook-addon-remix-react-router"; import TaskPage from "./TaskPage"; @@ -220,87 +220,70 @@ export const WaitingStartupScripts: Story = { export const SidebarAppNotFound: Story = { beforeEach: () => { - const workspace = mockTaskWorkspace(MockClaudeCodeApp, MockVSCodeApp); - spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); - spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue({ - ...workspace, - latest_build: { - ...workspace.latest_build, - task_app_id: "non-existent-app-id", - }, + const [task, workspace] = mockTaskWithWorkspace( + MockClaudeCodeApp, + MockVSCodeApp, + ); + spyOn(API.experimental, "getTask").mockResolvedValue({ + ...task, + workspace_app_id: null, }); + spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace); }, }; export const SidebarAppHealthDisabled: Story = { beforeEach: () => { - spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); - spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue( - mockTaskWorkspace( - { - ...MockClaudeCodeApp, - health: "disabled", - }, - MockVSCodeApp, - ), + const [task, workspace] = mockTaskWithWorkspace( + { ...MockClaudeCodeApp, health: "disabled" }, + MockVSCodeApp, ); + spyOn(API.experimental, "getTask").mockResolvedValue(task); + spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace); }, }; export const SidebarAppInitializing: Story = { beforeEach: () => { - spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); - spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue( - mockTaskWorkspace( - { - ...MockClaudeCodeApp, - health: "initializing", - }, - MockVSCodeApp, - ), + const [task, workspace] = mockTaskWithWorkspace( + { ...MockClaudeCodeApp, health: "initializing" }, + MockVSCodeApp, ); + spyOn(API.experimental, "getTask").mockResolvedValue(task); + spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace); }, }; export const SidebarAppHealthy: Story = { beforeEach: () => { - spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); - spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue( - mockTaskWorkspace( - { - ...MockClaudeCodeApp, - health: "healthy", - }, - MockVSCodeApp, - ), + const [task, workspace] = mockTaskWithWorkspace( + { ...MockClaudeCodeApp, health: "healthy" }, + MockVSCodeApp, ); + spyOn(API.experimental, "getTask").mockResolvedValue(task); + spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace); }, }; export const SidebarAppUnhealthy: Story = { beforeEach: () => { - spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); - spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue( - mockTaskWorkspace( - { - ...MockClaudeCodeApp, - health: "unhealthy", - }, - MockVSCodeApp, - ), + const [task, workspace] = mockTaskWithWorkspace( + { ...MockClaudeCodeApp, health: "unhealthy" }, + MockVSCodeApp, ); + spyOn(API.experimental, "getTask").mockResolvedValue(task); + spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace); }, }; const mainAppHealthStory = (health: WorkspaceApp["health"]) => ({ beforeEach: () => { - spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); - spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue( - mockTaskWorkspace(MockClaudeCodeApp, { - ...MockVSCodeApp, - health, - }), - ); + const [task, workspace] = mockTaskWithWorkspace(MockClaudeCodeApp, { + ...MockVSCodeApp, + health, + }); + spyOn(API.experimental, "getTask").mockResolvedValue(task); + spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace); }, }); @@ -311,10 +294,12 @@ export const MainAppUnhealthy: Story = mainAppHealthStory("unhealthy"); export const Active: Story = { decorators: [withProxyProvider()], beforeEach: () => { - spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); - spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue( - mockTaskWorkspace(MockClaudeCodeApp, MockVSCodeApp), + const [task, workspace] = mockTaskWithWorkspace( + MockClaudeCodeApp, + MockVSCodeApp, ); + spyOn(API.experimental, "getTask").mockResolvedValue(task); + spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace); }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -332,10 +317,12 @@ export const Active: Story = { export const ActivePreview: Story = { decorators: [withProxyProvider()], beforeEach: () => { - spyOn(API.experimental, "getTask").mockResolvedValue(MockTask); - spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue( - mockTaskWorkspace(MockClaudeCodeApp, MockVSCodeApp), + const [task, workspace] = mockTaskWithWorkspace( + MockClaudeCodeApp, + MockVSCodeApp, ); + spyOn(API.experimental, "getTask").mockResolvedValue(task); + spyOn(API, "getWorkspaceByOwnerAndName").mockResolvedValue(workspace); }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -464,51 +451,56 @@ export const WorkspaceStartFailureWithDialog: Story = { }, }; -function mockTaskWorkspace( +function mockTaskWithWorkspace( sidebarApp: WorkspaceApp, activeApp: WorkspaceApp, -): Workspace { - return { - ...MockWorkspace, - latest_build: { - ...MockWorkspace.latest_build, - has_ai_task: true, - task_app_id: sidebarApp.id, - resources: [ - { - ...MockWorkspaceResource, - agents: [ - { - ...MockWorkspaceAgentReady, - apps: [ - sidebarApp, - activeApp, - { - ...MockWorkspaceApp, - slug: "zed", - id: "zed", - display_name: "Zed", - icon: "/icon/zed.svg", - health: "healthy", - }, - { - ...MockWorkspaceApp, - slug: "preview", - id: "preview", - display_name: "Preview", - health: "healthy", - }, - { - ...MockWorkspaceApp, - slug: "disabled", - id: "disabled", - display_name: "Disabled", - }, - ], - }, - ], - }, - ], +): [Task, Workspace] { + return [ + { + ...MockTask, + workspace_app_id: sidebarApp.id, + }, + { + ...MockWorkspace, + latest_build: { + ...MockWorkspace.latest_build, + has_ai_task: true, + resources: [ + { + ...MockWorkspaceResource, + agents: [ + { + ...MockWorkspaceAgentReady, + apps: [ + sidebarApp, + activeApp, + { + ...MockWorkspaceApp, + slug: "zed", + id: "zed", + display_name: "Zed", + icon: "/icon/zed.svg", + health: "healthy", + }, + { + ...MockWorkspaceApp, + slug: "preview", + id: "preview", + display_name: "Preview", + health: "healthy", + }, + { + ...MockWorkspaceApp, + slug: "disabled", + id: "disabled", + display_name: "Disabled", + }, + ], + }, + ], + }, + ], + }, }, - }; + ]; } diff --git a/site/src/pages/TaskPage/TaskPage.tsx b/site/src/pages/TaskPage/TaskPage.tsx index 49f9d7569680b..f57d0e9f1772c 100644 --- a/site/src/pages/TaskPage/TaskPage.tsx +++ b/site/src/pages/TaskPage/TaskPage.tsx @@ -149,7 +149,7 @@ const TaskPage = () => { content = ; } else { const chatApp = getAllAppsWithAgent(workspace).find( - (app) => app.id === workspace.latest_build.task_app_id, + (app) => app.id === task.workspace_app_id, ); content = ( @@ -174,7 +174,7 @@ const TaskPage = () => {
- + ); From 30d2fc8bfcb7ffab5a405b7ec07fe1eca34062a4 Mon Sep 17 00:00:00 2001 From: Rowan Smith Date: Fri, 31 Oct 2025 05:22:23 +1100 Subject: [PATCH 036/255] fix: fix incorrect rendering of RBAC in Helm chart when workspacePerms=false (#20569) --- .../tests/testdata/namespace_rbac.golden | 43 ------------------- .../testdata/namespace_rbac_coder.golden | 43 ------------------- helm/libcoder/templates/_rbac.yaml | 6 ++- 3 files changed, 4 insertions(+), 88 deletions(-) diff --git a/helm/coder/tests/testdata/namespace_rbac.golden b/helm/coder/tests/testdata/namespace_rbac.golden index 68650a02b3fb4..57a4ba3e2b214 100644 --- a/helm/coder/tests/testdata/namespace_rbac.golden +++ b/helm/coder/tests/testdata/namespace_rbac.golden @@ -117,34 +117,6 @@ rules: # Source: coder/templates/rbac.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: Role -metadata: - name: coder-workspace-perms - namespace: test-namespace2 -rules: - - apiGroups: - - apps - resources: - - deployments - verbs: - - create - - delete - - deletecollection - - get - - list - - patch - - update - - watch - - apiGroups: - - networking.k8s.io - resources: - - ingresses - verbs: - - get - - list ---- -# Source: coder/templates/rbac.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role metadata: name: coder-workspace-perms namespace: test-namespace3 @@ -262,21 +234,6 @@ roleRef: # Source: coder/templates/rbac.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding -metadata: - name: "coder" - namespace: test-namespace2 -subjects: - - kind: ServiceAccount - name: "coder" - namespace: default -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: coder-workspace-perms ---- -# Source: coder/templates/rbac.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding metadata: name: "coder" namespace: test-namespace3 diff --git a/helm/coder/tests/testdata/namespace_rbac_coder.golden b/helm/coder/tests/testdata/namespace_rbac_coder.golden index 239eb73f8ee51..2687504879629 100644 --- a/helm/coder/tests/testdata/namespace_rbac_coder.golden +++ b/helm/coder/tests/testdata/namespace_rbac_coder.golden @@ -117,34 +117,6 @@ rules: # Source: coder/templates/rbac.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: Role -metadata: - name: coder-workspace-perms - namespace: test-namespace2 -rules: - - apiGroups: - - apps - resources: - - deployments - verbs: - - create - - delete - - deletecollection - - get - - list - - patch - - update - - watch - - apiGroups: - - networking.k8s.io - resources: - - ingresses - verbs: - - get - - list ---- -# Source: coder/templates/rbac.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role metadata: name: coder-workspace-perms namespace: test-namespace3 @@ -262,21 +234,6 @@ roleRef: # Source: coder/templates/rbac.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding -metadata: - name: "coder" - namespace: test-namespace2 -subjects: - - kind: ServiceAccount - name: "coder" - namespace: coder -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: coder-workspace-perms ---- -# Source: coder/templates/rbac.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding metadata: name: "coder" namespace: test-namespace3 diff --git a/helm/libcoder/templates/_rbac.yaml b/helm/libcoder/templates/_rbac.yaml index 73ba2bd4e1394..633a8252e8a0f 100644 --- a/helm/libcoder/templates/_rbac.yaml +++ b/helm/libcoder/templates/_rbac.yaml @@ -1,7 +1,9 @@ {{- define "libcoder.rbac.forNamespace" -}} {{- $nsPerms := ternary .workspacePerms .Top.Values.coder.serviceAccount.workspacePerms (hasKey . "workspacePerms") -}} - {{- $nsDeploy := ternary .enableDeployments .Top.Values.coder.serviceAccount.enableDeployments (hasKey . "enableDeployments") -}} - {{- $nsExtra := ternary .extraRules .Top.Values.coder.serviceAccount.extraRules (hasKey . "extraRules") -}} + {{- $nsDeployRaw := ternary .enableDeployments .Top.Values.coder.serviceAccount.enableDeployments (hasKey . "enableDeployments") -}} + {{- $nsExtraRaw := ternary .extraRules .Top.Values.coder.serviceAccount.extraRules (hasKey . "extraRules") -}} + {{- $nsDeploy := and $nsPerms $nsDeployRaw -}} + {{- $nsExtra := ternary $nsExtraRaw (list) $nsPerms -}} {{- if or $nsPerms (or $nsDeploy $nsExtra) }} --- From d306a2d7e571ff68d58384a0a697480a394b55e9 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 30 Oct 2025 10:23:52 -0800 Subject: [PATCH 037/255] chore: log with %s on unexpected non-sdk err (#20570) With `%w` it prints an address instead of the error, like ` 0xc001329370` instead of ` : some error`, honestly idk why you even can log with `%w` it seems like it makes no sense to use `%w` outside of `fmt.Errorf`. This is to help debug https://github.com/coder/internal/issues/1010. --- coderd/coderdtest/coderdtest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 463ee888f6f22..ac362295f0e00 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -1604,7 +1604,7 @@ func (nopcloser) Close() error { return nil } // SDKError coerces err into an SDK error. func SDKError(t testing.TB, err error) *codersdk.Error { var cerr *codersdk.Error - require.True(t, errors.As(err, &cerr), "should be SDK error, got %w", err) + require.True(t, errors.As(err, &cerr), "should be SDK error, got %s", err) return cerr } From 139dab7cfe4af1c1b790624aa3d2596eb7796ed8 Mon Sep 17 00:00:00 2001 From: Zach <3724288+zedkipp@users.noreply.github.com> Date: Thu, 30 Oct 2025 23:41:08 +0000 Subject: [PATCH 038/255] feat(cli): optionally store session token in OS keyring (#20256) This change implements optional secure storage of the CLI token using the operating system keyring for Windows, with groundwork laid for macOS in a future change. Previously, the Coder CLI stored authentication tokens in plaintext configuration files, which posed a security risk because users' tokens are stored unencrypted and can be easily accessed by other processes or users with file system access. The keyring is opt-in to preserve compatibility with applications (like the JetBrains Toolbox plugin, VS code plugin, etc). Users can opt into keyring use with a new `--use-keyring` flag. The secure storage is platform dependent. Windows Credential Manager API is used on Windows. The session token continues to be stored in plain text on macOS and Linux. macOS is omitted for now while we figure out the best path forward for compatibility with apps like Coder Desktop. https://www.notion.so/coderhq/CLI-Session-Token-in-OS-Keyring-293d579be592808b8b7fd235304e50d5 https://github.com/coder/coder/issues/19403 --- cli/keyring_test.go | 355 ++++++++++++++++++ cli/login.go | 29 +- cli/logout.go | 11 +- cli/root.go | 57 ++- cli/sessionstore/sessionstore.go | 239 ++++++++++++ .../sessionstore_internal_test.go | 121 ++++++ cli/sessionstore/sessionstore_other.go | 19 + cli/sessionstore/sessionstore_test.go | 342 +++++++++++++++++ cli/sessionstore/sessionstore_windows.go | 66 ++++ cli/sessionstore/sessionstore_windows_test.go | 127 +++++++ cli/testdata/coder_--help.golden | 5 + cli/testdata/coder_login_--help.golden | 4 + docs/reference/cli/index.md | 9 + docs/reference/cli/login.md | 6 + enterprise/cli/testdata/coder_--help.golden | 5 + go.mod | 1 + go.sum | 2 + 17 files changed, 1383 insertions(+), 15 deletions(-) create mode 100644 cli/keyring_test.go create mode 100644 cli/sessionstore/sessionstore.go create mode 100644 cli/sessionstore/sessionstore_internal_test.go create mode 100644 cli/sessionstore/sessionstore_other.go create mode 100644 cli/sessionstore/sessionstore_test.go create mode 100644 cli/sessionstore/sessionstore_windows.go create mode 100644 cli/sessionstore/sessionstore_windows_test.go diff --git a/cli/keyring_test.go b/cli/keyring_test.go new file mode 100644 index 0000000000000..1a4e90895a6eb --- /dev/null +++ b/cli/keyring_test.go @@ -0,0 +1,355 @@ +package cli_test + +import ( + "bytes" + "net/url" + "os" + "path" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/cli" + "github.com/coder/coder/v2/cli/clitest" + "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/pty/ptytest" +) + +// mockKeyring is a mock sessionstore.Backend implementation. +type mockKeyring struct { + credentials map[string]string // service name -> credential +} + +const mockServiceName = "mock-service-name" + +func newMockKeyring() *mockKeyring { + return &mockKeyring{credentials: make(map[string]string)} +} + +func (m *mockKeyring) Read(_ *url.URL) (string, error) { + cred, ok := m.credentials[mockServiceName] + if !ok { + return "", os.ErrNotExist + } + return cred, nil +} + +func (m *mockKeyring) Write(_ *url.URL, token string) error { + m.credentials[mockServiceName] = token + return nil +} + +func (m *mockKeyring) Delete(_ *url.URL) error { + _, ok := m.credentials[mockServiceName] + if !ok { + return os.ErrNotExist + } + delete(m.credentials, mockServiceName) + return nil +} + +func TestUseKeyring(t *testing.T) { + // Verify that the --use-keyring flag opts into using a keyring backend for + // storing session tokens instead of plain text files. + t.Parallel() + + t.Run("Login", func(t *testing.T) { + t.Parallel() + + // Create a test server + client := coderdtest.New(t, nil) + coderdtest.CreateFirstUser(t, client) + + // Create a pty for interactive prompts + pty := ptytest.New(t) + + // Create CLI invocation with --use-keyring flag + inv, cfg := clitest.New(t, + "login", + "--force-tty", + "--use-keyring", + "--no-open", + client.URL.String(), + ) + inv.Stdin = pty.Input() + inv.Stdout = pty.Output() + + // Inject the mock backend before running the command + var root cli.RootCmd + cmd, err := root.Command(root.AGPL()) + require.NoError(t, err) + mockBackend := newMockKeyring() + root.WithSessionStorageBackend(mockBackend) + inv.Command = cmd + + // Run login in background + doneChan := make(chan struct{}) + go func() { + defer close(doneChan) + err := inv.Run() + assert.NoError(t, err) + }() + + // Provide the token when prompted + pty.ExpectMatch("Paste your token here:") + pty.WriteLine(client.SessionToken()) + pty.ExpectMatch("Welcome to Coder") + <-doneChan + + // Verify that session file was NOT created (using keyring instead) + sessionFile := path.Join(string(cfg), "session") + _, err = os.Stat(sessionFile) + require.True(t, os.IsNotExist(err), "session file should not exist when using keyring") + + // Verify that the credential IS stored in mock keyring + cred, err := mockBackend.Read(nil) + require.NoError(t, err, "credential should be stored in mock keyring") + require.Equal(t, client.SessionToken(), cred, "stored token should match login token") + }) + + t.Run("Logout", func(t *testing.T) { + t.Parallel() + + // Create a test server + client := coderdtest.New(t, nil) + coderdtest.CreateFirstUser(t, client) + + // Create a pty for interactive prompts + pty := ptytest.New(t) + + // First, login with --use-keyring + loginInv, cfg := clitest.New(t, + "login", + "--force-tty", + "--use-keyring", + "--no-open", + client.URL.String(), + ) + loginInv.Stdin = pty.Input() + loginInv.Stdout = pty.Output() + + // Inject the mock backend + var loginRoot cli.RootCmd + loginCmd, err := loginRoot.Command(loginRoot.AGPL()) + require.NoError(t, err) + mockBackend := newMockKeyring() + loginRoot.WithSessionStorageBackend(mockBackend) + loginInv.Command = loginCmd + + doneChan := make(chan struct{}) + go func() { + defer close(doneChan) + err := loginInv.Run() + assert.NoError(t, err) + }() + + pty.ExpectMatch("Paste your token here:") + pty.WriteLine(client.SessionToken()) + pty.ExpectMatch("Welcome to Coder") + <-doneChan + + // Verify credential exists in mock keyring + cred, err := mockBackend.Read(nil) + require.NoError(t, err, "read credential should succeed before logout") + require.NotEmpty(t, cred, "credential should exist after logout") + + // Now run logout with --use-keyring + logoutInv, _ := clitest.New(t, + "logout", + "--use-keyring", + "--yes", + "--global-config", string(cfg), + ) + + // Inject the same mock backend + var logoutRoot cli.RootCmd + logoutCmd, err := logoutRoot.Command(logoutRoot.AGPL()) + require.NoError(t, err) + logoutRoot.WithSessionStorageBackend(mockBackend) + logoutInv.Command = logoutCmd + + var logoutOut bytes.Buffer + logoutInv.Stdout = &logoutOut + + err = logoutInv.Run() + require.NoError(t, err, "logout should succeed") + + // Verify the credential was deleted from mock keyring + _, err = mockBackend.Read(nil) + require.ErrorIs(t, err, os.ErrNotExist, "credential should be deleted from keyring after logout") + }) + + t.Run("OmitFlag", func(t *testing.T) { + t.Parallel() + + // Create a test server + client := coderdtest.New(t, nil) + coderdtest.CreateFirstUser(t, client) + + // Create a pty for interactive prompts + pty := ptytest.New(t) + + // --use-keyring flag omitted (should use file-based storage) + inv, cfg := clitest.New(t, + "login", + "--force-tty", + "--no-open", + client.URL.String(), + ) + inv.Stdin = pty.Input() + inv.Stdout = pty.Output() + + doneChan := make(chan struct{}) + go func() { + defer close(doneChan) + err := inv.Run() + assert.NoError(t, err) + }() + + pty.ExpectMatch("Paste your token here:") + pty.WriteLine(client.SessionToken()) + pty.ExpectMatch("Welcome to Coder") + <-doneChan + + // Verify that session file WAS created (not using keyring) + sessionFile := path.Join(string(cfg), "session") + _, err := os.Stat(sessionFile) + require.NoError(t, err, "session file should exist when NOT using --use-keyring") + + // Read and verify the token from file + content, err := os.ReadFile(sessionFile) + require.NoError(t, err, "should be able to read session file") + require.Equal(t, client.SessionToken(), string(content), "file should contain the session token") + }) + + t.Run("EnvironmentVariable", func(t *testing.T) { + t.Parallel() + + // Create a test server + client := coderdtest.New(t, nil) + coderdtest.CreateFirstUser(t, client) + + // Create a pty for interactive prompts + pty := ptytest.New(t) + + // Login using CODER_USE_KEYRING environment variable instead of flag + inv, cfg := clitest.New(t, + "login", + "--force-tty", + "--no-open", + client.URL.String(), + ) + inv.Stdin = pty.Input() + inv.Stdout = pty.Output() + inv.Environ.Set("CODER_USE_KEYRING", "true") + + // Inject the mock backend + var root cli.RootCmd + cmd, err := root.Command(root.AGPL()) + require.NoError(t, err) + mockBackend := newMockKeyring() + root.WithSessionStorageBackend(mockBackend) + inv.Command = cmd + + doneChan := make(chan struct{}) + go func() { + defer close(doneChan) + err := inv.Run() + assert.NoError(t, err) + }() + + pty.ExpectMatch("Paste your token here:") + pty.WriteLine(client.SessionToken()) + pty.ExpectMatch("Welcome to Coder") + <-doneChan + + // Verify that session file was NOT created (using keyring via env var) + sessionFile := path.Join(string(cfg), "session") + _, err = os.Stat(sessionFile) + require.True(t, os.IsNotExist(err), "session file should not exist when using keyring via env var") + + // Verify credential is in mock keyring + cred, err := mockBackend.Read(nil) + require.NoError(t, err, "credential should be stored in keyring when CODER_USE_KEYRING=true") + require.NotEmpty(t, cred) + }) +} + +func TestUseKeyringUnsupportedOS(t *testing.T) { + // Verify that trying to use --use-keyring on an unsupported operating system produces + // a helpful error message. + t.Parallel() + + // Skip on Windows since the keyring is actually supported. + if runtime.GOOS == "windows" { + t.Skip("Skipping unsupported OS test on Windows where keyring is supported") + } + + const expMessage = "keyring storage is not supported on this operating system; remove the --use-keyring flag" + + t.Run("LoginWithUnsupportedKeyring", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, nil) + coderdtest.CreateFirstUser(t, client) + + // Try to login with --use-keyring on an unsupported OS + inv, _ := clitest.New(t, + "login", + "--use-keyring", + client.URL.String(), + ) + + // The error should occur immediately, before any prompts + loginErr := inv.Run() + + // Verify we got an error about unsupported OS + require.Error(t, loginErr) + require.Contains(t, loginErr.Error(), expMessage) + }) + + t.Run("LogoutWithUnsupportedKeyring", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, nil) + coderdtest.CreateFirstUser(t, client) + pty := ptytest.New(t) + + // First login without keyring to create a session + loginInv, cfg := clitest.New(t, + "login", + "--force-tty", + "--no-open", + client.URL.String(), + ) + loginInv.Stdin = pty.Input() + loginInv.Stdout = pty.Output() + + doneChan := make(chan struct{}) + go func() { + defer close(doneChan) + err := loginInv.Run() + assert.NoError(t, err) + }() + + pty.ExpectMatch("Paste your token here:") + pty.WriteLine(client.SessionToken()) + pty.ExpectMatch("Welcome to Coder") + <-doneChan + + // Now try to logout with --use-keyring on an unsupported OS + logoutInv, _ := clitest.New(t, + "logout", + "--use-keyring", + "--yes", + "--global-config", string(cfg), + ) + + err := logoutInv.Run() + // Verify we got an error about unsupported OS + require.Error(t, err) + require.Contains(t, err.Error(), expMessage) + }) +} diff --git a/cli/login.go b/cli/login.go index fcba1ee50eb74..1a95d0403588f 100644 --- a/cli/login.go +++ b/cli/login.go @@ -19,6 +19,7 @@ import ( "github.com/coder/pretty" "github.com/coder/coder/v2/cli/cliui" + "github.com/coder/coder/v2/cli/sessionstore" "github.com/coder/coder/v2/coderd/userpassword" "github.com/coder/coder/v2/codersdk" "github.com/coder/serpent" @@ -114,9 +115,11 @@ func (r *RootCmd) loginWithPassword( } sessionToken := resp.SessionToken - config := r.createConfig() - err = config.Session().Write(sessionToken) + err = r.ensureTokenBackend().Write(client.URL, sessionToken) if err != nil { + if xerrors.Is(err, sessionstore.ErrNotImplemented) { + return errKeyringNotSupported + } return xerrors.Errorf("write session token: %w", err) } @@ -149,11 +152,15 @@ func (r *RootCmd) login() *serpent.Command { useTokenForSession bool ) cmd := &serpent.Command{ - Use: "login []", - Short: "Authenticate with Coder deployment", + Use: "login []", + Short: "Authenticate with Coder deployment", + Long: "By default, the session token is stored in a plain text file. Use the " + + "--use-keyring flag or set CODER_USE_KEYRING=true to store the token in " + + "the operating system keyring instead.", Middleware: serpent.RequireRangeArgs(0, 1), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + rawURL := "" var urlSource string @@ -198,6 +205,15 @@ func (r *RootCmd) login() *serpent.Command { return err } + // Check keyring availability before prompting the user for a token to fail fast. + if r.useKeyring { + backend := r.ensureTokenBackend() + _, err := backend.Read(client.URL) + if err != nil && xerrors.Is(err, sessionstore.ErrNotImplemented) { + return errKeyringNotSupported + } + } + hasFirstUser, err := client.HasFirstUser(ctx) if err != nil { return xerrors.Errorf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser? Error - has initial user: %w", serverURL.String(), err) @@ -394,8 +410,11 @@ func (r *RootCmd) login() *serpent.Command { } config := r.createConfig() - err = config.Session().Write(sessionToken) + err = r.ensureTokenBackend().Write(client.URL, sessionToken) if err != nil { + if xerrors.Is(err, sessionstore.ErrNotImplemented) { + return errKeyringNotSupported + } return xerrors.Errorf("write session token: %w", err) } err = config.URL().Write(serverURL.String()) diff --git a/cli/logout.go b/cli/logout.go index 33cd55cc81042..db10c3abe4315 100644 --- a/cli/logout.go +++ b/cli/logout.go @@ -8,6 +8,7 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/cli/cliui" + "github.com/coder/coder/v2/cli/sessionstore" "github.com/coder/serpent" ) @@ -46,11 +47,15 @@ func (r *RootCmd) logout() *serpent.Command { errors = append(errors, xerrors.Errorf("remove URL file: %w", err)) } - err = config.Session().Delete() + err = r.ensureTokenBackend().Delete(client.URL) // Only throw error if the session configuration file is present, // otherwise the user is already logged out, and we proceed - if err != nil && !os.IsNotExist(err) { - errors = append(errors, xerrors.Errorf("remove session file: %w", err)) + if err != nil && !xerrors.Is(err, os.ErrNotExist) { + if xerrors.Is(err, sessionstore.ErrNotImplemented) { + errors = append(errors, errKeyringNotSupported) + } else { + errors = append(errors, xerrors.Errorf("remove session token: %w", err)) + } } err = config.Organization().Delete() diff --git a/cli/root.go b/cli/root.go index c44c0625c2c34..fe6d5c4ccd8a9 100644 --- a/cli/root.go +++ b/cli/root.go @@ -37,6 +37,7 @@ import ( "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/cli/config" "github.com/coder/coder/v2/cli/gitauth" + "github.com/coder/coder/v2/cli/sessionstore" "github.com/coder/coder/v2/cli/telemetry" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" @@ -54,6 +55,8 @@ var ( // ErrSilent is a sentinel error that tells the command handler to just exit with a non-zero error, but not print // anything. ErrSilent = xerrors.New("silent error") + + errKeyringNotSupported = xerrors.New("keyring storage is not supported on this operating system; remove the --use-keyring flag to use file-based storage") ) const ( @@ -68,12 +71,14 @@ const ( varVerbose = "verbose" varDisableDirect = "disable-direct-connections" varDisableNetworkTelemetry = "disable-network-telemetry" + varUseKeyring = "use-keyring" notLoggedInMessage = "You are not logged in. Try logging in using '%s login '." envNoVersionCheck = "CODER_NO_VERSION_WARNING" envNoFeatureWarning = "CODER_NO_FEATURE_WARNING" envSessionToken = "CODER_SESSION_TOKEN" + envUseKeyring = "CODER_USE_KEYRING" //nolint:gosec envAgentToken = "CODER_AGENT_TOKEN" //nolint:gosec @@ -474,6 +479,15 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err Value: serpent.BoolOf(&r.disableNetworkTelemetry), Group: globalGroup, }, + { + Flag: varUseKeyring, + Env: envUseKeyring, + Description: "Store and retrieve session tokens using the operating system " + + "keyring. Currently only supported on Windows. By default, tokens are " + + "stored in plain text files.", + Value: serpent.BoolOf(&r.useKeyring), + Group: globalGroup, + }, { Flag: "debug-http", Description: "Debug codersdk HTTP requests.", @@ -508,6 +522,7 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err type RootCmd struct { clientURL *url.URL token string + tokenBackend sessionstore.Backend globalConfig string header []string headerCommand string @@ -522,6 +537,7 @@ type RootCmd struct { disableNetworkTelemetry bool noVersionCheck bool noFeatureWarning bool + useKeyring bool } // InitClient creates and configures a new client with authentication, telemetry, @@ -549,14 +565,19 @@ func (r *RootCmd) InitClient(inv *serpent.Invocation) (*codersdk.Client, error) return nil, err } } - // Read the token stored on disk. if r.token == "" { - r.token, err = conf.Session().Read() + tok, err := r.ensureTokenBackend().Read(r.clientURL) // Even if there isn't a token, we don't care. // Some API routes can be unauthenticated. - if err != nil && !os.IsNotExist(err) { + if err != nil && !xerrors.Is(err, os.ErrNotExist) { + if xerrors.Is(err, sessionstore.ErrNotImplemented) { + return nil, errKeyringNotSupported + } return nil, err } + if tok != "" { + r.token = tok + } } // Configure HTTP client with transport wrappers @@ -588,7 +609,6 @@ func (r *RootCmd) InitClient(inv *serpent.Invocation) (*codersdk.Client, error) // This allows commands to run without requiring authentication, but still use auth if available. func (r *RootCmd) TryInitClient(inv *serpent.Invocation) (*codersdk.Client, error) { conf := r.createConfig() - var err error // Read the client URL stored on disk. if r.clientURL == nil || r.clientURL.String() == "" { rawURL, err := conf.URL().Read() @@ -605,14 +625,19 @@ func (r *RootCmd) TryInitClient(inv *serpent.Invocation) (*codersdk.Client, erro } } } - // Read the token stored on disk. if r.token == "" { - r.token, err = conf.Session().Read() + tok, err := r.ensureTokenBackend().Read(r.clientURL) // Even if there isn't a token, we don't care. // Some API routes can be unauthenticated. - if err != nil && !os.IsNotExist(err) { + if err != nil && !xerrors.Is(err, os.ErrNotExist) { + if xerrors.Is(err, sessionstore.ErrNotImplemented) { + return nil, errKeyringNotSupported + } return nil, err } + if tok != "" { + r.token = tok + } } // Only configure the client if we have a URL @@ -688,6 +713,24 @@ func (r *RootCmd) createUnauthenticatedClient(ctx context.Context, serverURL *ur return client, nil } +// ensureTokenBackend returns the session token storage backend, creating it if necessary. +// This must be called after flags are parsed so we can respect the value of the --use-keyring +// flag. +func (r *RootCmd) ensureTokenBackend() sessionstore.Backend { + if r.tokenBackend == nil { + if r.useKeyring { + r.tokenBackend = sessionstore.NewKeyring() + } else { + r.tokenBackend = sessionstore.NewFile(r.createConfig) + } + } + return r.tokenBackend +} + +func (r *RootCmd) WithSessionStorageBackend(backend sessionstore.Backend) { + r.tokenBackend = backend +} + type AgentAuth struct { // Agent Client config agentToken string diff --git a/cli/sessionstore/sessionstore.go b/cli/sessionstore/sessionstore.go new file mode 100644 index 0000000000000..8ae14c2786c2f --- /dev/null +++ b/cli/sessionstore/sessionstore.go @@ -0,0 +1,239 @@ +// Package sessionstore provides CLI session token storage mechanisms. +// Operating system keyring storage is intended to have compatibility with other Coder +// applications (e.g. Coder Desktop, Coder provider for JetBrains Toolbox, etc) so that +// applications can read/write the same credential stored in the keyring. +// +// Note that we aren't using an existing Go package zalando/go-keyring here for a few +// reasons. 1) It prescribes the format of the target credential name in the OS keyrings, +// which makes our life difficult for compatibility with other Coder applications. 2) +// It uses init functions that make it difficult to test with. As a result, the OS +// keyring implementations may be adapted from zalando/go-keyring source (i.e. Windows). +package sessionstore + +import ( + "encoding/json" + "errors" + "net/url" + "os" + "strings" + + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/cli/config" +) + +// Backend is a storage backend for session tokens. +type Backend interface { + // Read returns the session token for the given server URL or an error, if any. It + // will return os.ErrNotExist if no token exists for the given URL. + Read(serverURL *url.URL) (string, error) + // Write stores the session token for the given server URL. + Write(serverURL *url.URL, token string) error + // Delete removes the session token for the given server URL or an error, if any. + // It will return os.ErrNotExist error if no token exists to delete. + Delete(serverURL *url.URL) error +} + +var ( + + // ErrSetDataTooBig is returned if `keyringProvider.Set` was called with too much data. + // On macOS: The combination of service, username & password should not exceed ~3000 bytes + // On Windows: The service is limited to 32KiB while the password is limited to 2560 bytes + ErrSetDataTooBig = xerrors.New("data passed to Set was too big") + + // ErrNotImplemented represents when keyring usage is not implemented on the current + // operating system. + ErrNotImplemented = xerrors.New("not implemented") +) + +// keyringProvider represents an operating system keyring. The expectation +// is these methods operate on the user/login keyring. +type keyringProvider interface { + // Set stores the given credential for a service name in the operating system + // keyring. + Set(service, credential string) error + // Get retrieves the credential from the keyring. It must return os.ErrNotExist + // if the credential is not found. + Get(service string) ([]byte, error) + // Delete deletes the credential from the keyring. It must return os.ErrNotExist + // if the credential is not found. + Delete(service string) error +} + +// credential represents a single credential entry. +type credential struct { + CoderURL string `json:"coder_url"` + APIToken string `json:"api_token"` +} + +// credentialsMap represents the JSON structure stored in the operating system keyring. +// It supports storing multiple credentials for different server URLs. +type credentialsMap map[string]credential + +// normalizeHost returns a normalized version of the URL host for use as a map key. +func normalizeHost(u *url.URL) (string, error) { + if u == nil || u.Host == "" { + return "", xerrors.New("nil server URL") + } + return strings.TrimSpace(strings.ToLower(u.Host)), nil +} + +// parseCredentialsJSON parses the JSON from the keyring into a credentialsMap. +func parseCredentialsJSON(jsonData []byte) (credentialsMap, error) { + if len(jsonData) == 0 { + return make(credentialsMap), nil + } + + var creds credentialsMap + if err := json.Unmarshal(jsonData, &creds); err != nil { + return nil, xerrors.Errorf("unmarshal credentials: %w", err) + } + + return creds, nil +} + +// Keyring is a Backend that exclusively stores the session token in the operating +// system keyring. Happy path usage of this type should start with NewKeyring. +// It stores a JSON object in the keyring that supports multiple credentials for +// different server URLs, providing compatibility with Coder Desktop and other Coder +// applications. +type Keyring struct { + provider keyringProvider + serviceName string +} + +// NewKeyring creates a Keyring with the default service name for production use. +func NewKeyring() Keyring { + return Keyring{ + provider: operatingSystemKeyring{}, + serviceName: defaultServiceName, + } +} + +// NewKeyringWithService creates a Keyring Backend that stores credentials under the +// specified service name. This is primarily intended for testing to avoid conflicts +// with production credentials and collisions between tests. +func NewKeyringWithService(serviceName string) Keyring { + return Keyring{ + provider: operatingSystemKeyring{}, + serviceName: serviceName, + } +} + +func (o Keyring) Read(serverURL *url.URL) (string, error) { + host, err := normalizeHost(serverURL) + if err != nil { + return "", err + } + + credJSON, err := o.provider.Get(o.serviceName) + if err != nil { + return "", err + } + if len(credJSON) == 0 { + return "", os.ErrNotExist + } + + creds, err := parseCredentialsJSON(credJSON) + if err != nil { + return "", xerrors.Errorf("read: parse existing credentials: %w", err) + } + + // Return the credential for the specified URL + cred, ok := creds[host] + if !ok { + return "", os.ErrNotExist + } + return cred.APIToken, nil +} + +func (o Keyring) Write(serverURL *url.URL, token string) error { + host, err := normalizeHost(serverURL) + if err != nil { + return err + } + + existingJSON, err := o.provider.Get(o.serviceName) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return xerrors.Errorf("read existing credentials: %w", err) + } + + creds, err := parseCredentialsJSON(existingJSON) + if err != nil { + return xerrors.Errorf("write: parse existing credentials: %w", err) + } + + // Upsert the credential for this URL. + creds[host] = credential{ + CoderURL: host, + APIToken: token, + } + + credsJSON, err := json.Marshal(creds) + if err != nil { + return xerrors.Errorf("marshal credentials: %w", err) + } + + err = o.provider.Set(o.serviceName, string(credsJSON)) + if err != nil { + return xerrors.Errorf("write credentials to keyring: %w", err) + } + return nil +} + +func (o Keyring) Delete(serverURL *url.URL) error { + host, err := normalizeHost(serverURL) + if err != nil { + return err + } + + existingJSON, err := o.provider.Get(o.serviceName) + if err != nil { + return err + } + + creds, err := parseCredentialsJSON(existingJSON) + if err != nil { + return xerrors.Errorf("failed to parse existing credentials: %w", err) + } + + if _, ok := creds[host]; !ok { + return os.ErrNotExist + } + + delete(creds, host) + + // Delete the entire keyring entry when no credentials remain. + if len(creds) == 0 { + return o.provider.Delete(o.serviceName) + } + + // Write back the updated credentials map. + credsJSON, err := json.Marshal(creds) + if err != nil { + return xerrors.Errorf("failed to marshal credentials: %w", err) + } + + return o.provider.Set(o.serviceName, string(credsJSON)) +} + +// File is a Backend that exclusively stores the session token in a file on disk. +type File struct { + config func() config.Root +} + +func NewFile(f func() config.Root) *File { + return &File{config: f} +} + +func (f *File) Read(_ *url.URL) (string, error) { + return f.config().Session().Read() +} + +func (f *File) Write(_ *url.URL, token string) error { + return f.config().Session().Write(token) +} + +func (f *File) Delete(_ *url.URL) error { + return f.config().Session().Delete() +} diff --git a/cli/sessionstore/sessionstore_internal_test.go b/cli/sessionstore/sessionstore_internal_test.go new file mode 100644 index 0000000000000..baf2efa2f49d6 --- /dev/null +++ b/cli/sessionstore/sessionstore_internal_test.go @@ -0,0 +1,121 @@ +package sessionstore + +import ( + "encoding/json" + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNormalizeHost(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + url *url.URL + want string + wantErr bool + }{ + { + name: "StandardHost", + url: &url.URL{Host: "coder.example.com"}, + want: "coder.example.com", + }, + { + name: "HostWithPort", + url: &url.URL{Host: "coder.example.com:8080"}, + want: "coder.example.com:8080", + }, + { + name: "UppercaseHost", + url: &url.URL{Host: "CODER.EXAMPLE.COM"}, + want: "coder.example.com", + }, + { + name: "HostWithWhitespace", + url: &url.URL{Host: " coder.example.com "}, + want: "coder.example.com", + }, + { + name: "NilURL", + url: nil, + want: "", + wantErr: true, + }, + { + name: "EmptyHost", + url: &url.URL{Host: ""}, + want: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := normalizeHost(tt.url) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} + +func TestParseCredentialsJSON(t *testing.T) { + t.Parallel() + + t.Run("Empty", func(t *testing.T) { + t.Parallel() + creds, err := parseCredentialsJSON(nil) + require.NoError(t, err) + require.NotNil(t, creds) + require.Empty(t, creds) + }) + + t.Run("NewFormat", func(t *testing.T) { + t.Parallel() + jsonData := []byte(`{ + "coder1.example.com": {"coder_url": "coder1.example.com", "api_token": "token1"}, + "coder2.example.com": {"coder_url": "coder2.example.com", "api_token": "token2"} + }`) + creds, err := parseCredentialsJSON(jsonData) + require.NoError(t, err) + require.Len(t, creds, 2) + require.Equal(t, "token1", creds["coder1.example.com"].APIToken) + require.Equal(t, "token2", creds["coder2.example.com"].APIToken) + }) + + t.Run("InvalidJSON", func(t *testing.T) { + t.Parallel() + jsonData := []byte(`{invalid json}`) + _, err := parseCredentialsJSON(jsonData) + require.Error(t, err) + }) +} + +func TestCredentialsMap_RoundTrip(t *testing.T) { + t.Parallel() + + creds := credentialsMap{ + "coder1.example.com": { + CoderURL: "coder1.example.com", + APIToken: "token1", + }, + "coder2.example.com:8080": { + CoderURL: "coder2.example.com:8080", + APIToken: "token2", + }, + } + + jsonData, err := json.Marshal(creds) + require.NoError(t, err) + + parsed, err := parseCredentialsJSON(jsonData) + require.NoError(t, err) + + require.Equal(t, creds, parsed) +} diff --git a/cli/sessionstore/sessionstore_other.go b/cli/sessionstore/sessionstore_other.go new file mode 100644 index 0000000000000..d930790135aca --- /dev/null +++ b/cli/sessionstore/sessionstore_other.go @@ -0,0 +1,19 @@ +//go:build !windows + +package sessionstore + +const defaultServiceName = "not-implemented" + +type operatingSystemKeyring struct{} + +func (operatingSystemKeyring) Set(_, _ string) error { + return ErrNotImplemented +} + +func (operatingSystemKeyring) Get(_ string) ([]byte, error) { + return nil, ErrNotImplemented +} + +func (operatingSystemKeyring) Delete(_ string) error { + return ErrNotImplemented +} diff --git a/cli/sessionstore/sessionstore_test.go b/cli/sessionstore/sessionstore_test.go new file mode 100644 index 0000000000000..c01a1256eb085 --- /dev/null +++ b/cli/sessionstore/sessionstore_test.go @@ -0,0 +1,342 @@ +package sessionstore_test + +import ( + "errors" + "fmt" + "net/url" + "os" + "path" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/cli/config" + "github.com/coder/coder/v2/cli/sessionstore" +) + +// Generate a test service name for use with the OS keyring. It uses a combination +// of the test name and a nanosecond timestamp to prevent collisions. +func keyringTestServiceName(t *testing.T) string { + t.Helper() + return t.Name() + "_" + fmt.Sprintf("%v", time.Now().UnixNano()) +} + +func TestKeyring(t *testing.T) { + t.Parallel() + + if runtime.GOOS != "windows" { + t.Skip("linux and darwin are not supported yet") + } + + // This test exercises use of the operating system keyring. As a result, + // the operating system keyring is expected to be available. + + const ( + testURL = "http://127.0.0.1:1337" + testURL2 = "http://127.0.0.1:1338" + ) + + t.Run("ReadNonExistent", func(t *testing.T) { + t.Parallel() + + backend := sessionstore.NewKeyringWithService(keyringTestServiceName(t)) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + t.Cleanup(func() { _ = backend.Delete(srvURL) }) + + _, err = backend.Read(srvURL) + require.Error(t, err) + require.True(t, os.IsNotExist(err), "expected os.ErrNotExist when reading non-existent token") + }) + + t.Run("DeleteNonExistent", func(t *testing.T) { + t.Parallel() + + backend := sessionstore.NewKeyringWithService(keyringTestServiceName(t)) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + t.Cleanup(func() { _ = backend.Delete(srvURL) }) + + err = backend.Delete(srvURL) + require.Error(t, err) + require.True(t, errors.Is(err, os.ErrNotExist), "expected os.ErrNotExist when deleting non-existent token") + }) + + t.Run("WriteAndRead", func(t *testing.T) { + t.Parallel() + + backend := sessionstore.NewKeyringWithService(keyringTestServiceName(t)) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + t.Cleanup(func() { _ = backend.Delete(srvURL) }) + + dir := t.TempDir() + expSessionFile := path.Join(dir, "session") + + const inputToken = "test-keyring-token-12345" + err = backend.Write(srvURL, inputToken) + require.NoError(t, err) + + // Verify no session file was created (keyring stores in OS keyring, not file) + _, err = os.Stat(expSessionFile) + require.True(t, errors.Is(err, os.ErrNotExist), "expected session token file to not exist when using keyring") + + token, err := backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, inputToken, token) + + // Clean up + err = backend.Delete(srvURL) + require.NoError(t, err) + }) + + t.Run("WriteAndDelete", func(t *testing.T) { + t.Parallel() + + backend := sessionstore.NewKeyringWithService(keyringTestServiceName(t)) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + t.Cleanup(func() { _ = backend.Delete(srvURL) }) + + const inputToken = "test-keyring-token-67890" + err = backend.Write(srvURL, inputToken) + require.NoError(t, err) + + token, err := backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, inputToken, token) + + err = backend.Delete(srvURL) + require.NoError(t, err) + + _, err = backend.Read(srvURL) + require.Error(t, err) + require.True(t, os.IsNotExist(err), "expected os.ErrNotExist after deleting token") + }) + + t.Run("OverwriteToken", func(t *testing.T) { + t.Parallel() + + backend := sessionstore.NewKeyringWithService(keyringTestServiceName(t)) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + t.Cleanup(func() { _ = backend.Delete(srvURL) }) + + // Write first token + const firstToken = "first-keyring-token" + err = backend.Write(srvURL, firstToken) + require.NoError(t, err) + + token, err := backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, firstToken, token) + + // Overwrite with second token + const secondToken = "second-keyring-token" + err = backend.Write(srvURL, secondToken) + require.NoError(t, err) + + token, err = backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, secondToken, token) + + // Clean up + err = backend.Delete(srvURL) + require.NoError(t, err) + }) + + t.Run("MultipleServers", func(t *testing.T) { + t.Parallel() + + backend := sessionstore.NewKeyringWithService(keyringTestServiceName(t)) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + srvURL2, err := url.Parse(testURL2) + require.NoError(t, err) + + t.Cleanup(func() { + _ = backend.Delete(srvURL) + _ = backend.Delete(srvURL2) + }) + + // Write token for server 1 + const token1 = "token-for-server-1" + err = backend.Write(srvURL, token1) + require.NoError(t, err) + + // Write token for server 2 (should NOT overwrite server 1) + const token2 = "token-for-server-2" + err = backend.Write(srvURL2, token2) + require.NoError(t, err) + + // Read server 1's credential + token, err := backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, token1, token) + + // Read server 2's credential + token, err = backend.Read(srvURL2) + require.NoError(t, err) + require.Equal(t, token2, token) + + // Delete server 1's credential + err = backend.Delete(srvURL) + require.NoError(t, err) + + // Verify server 1's credential is gone + _, err = backend.Read(srvURL) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + + // Verify server 2's credential still exists + token, err = backend.Read(srvURL2) + require.NoError(t, err) + require.Equal(t, token2, token) + + // Clean up remaining credentials + err = backend.Delete(srvURL2) + require.NoError(t, err) + }) +} + +func TestFile(t *testing.T) { + const ( + testURL = "http://127.0.0.1:1337" + testURL2 = "http://127.0.0.1:1338" + ) + + t.Parallel() + + t.Run("ReadNonExistent", func(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + backend := sessionstore.NewFile(func() config.Root { return config.Root(dir) }) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + + _, err = backend.Read(srvURL) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + }) + + t.Run("WriteAndRead", func(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + backend := sessionstore.NewFile(func() config.Root { return config.Root(dir) }) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + + // Write a token + const inputToken = "test-token-12345" + err = backend.Write(srvURL, inputToken) + require.NoError(t, err) + + // Verify the session file was created + sessionFile := config.Root(dir).Session() + require.True(t, sessionFile.Exists()) + + // Read the token back + token, err := backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, inputToken, token) + }) + + t.Run("WriteAndDelete", func(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + backend := sessionstore.NewFile(func() config.Root { return config.Root(dir) }) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + + // Write a token + const inputToken = "test-token-67890" + err = backend.Write(srvURL, inputToken) + require.NoError(t, err) + + // Verify the token was written + token, err := backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, inputToken, token) + + // Delete the token + err = backend.Delete(srvURL) + require.NoError(t, err) + + // Verify the token is gone + _, err = backend.Read(srvURL) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + }) + + t.Run("DeleteNonExistent", func(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + backend := sessionstore.NewFile(func() config.Root { return config.Root(dir) }) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + + // Attempt to delete a non-existent token + err = backend.Delete(srvURL) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + }) + + t.Run("OverwriteToken", func(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + backend := sessionstore.NewFile(func() config.Root { return config.Root(dir) }) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + + // Write first token + const firstToken = "first-token" + err = backend.Write(srvURL, firstToken) + require.NoError(t, err) + + token, err := backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, firstToken, token) + + // Overwrite with second token + const secondToken = "second-token" + err = backend.Write(srvURL, secondToken) + require.NoError(t, err) + + token, err = backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, secondToken, token) + }) + + t.Run("WriteIgnoresURL", func(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + backend := sessionstore.NewFile(func() config.Root { return config.Root(dir) }) + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + srvURL2, err := url.Parse(testURL2) + require.NoError(t, err) + + //nolint:gosec // Write with first URL test token + const firstToken = "token-for-url1" + err = backend.Write(srvURL, firstToken) + require.NoError(t, err) + + //nolint:gosec // Write with second URL - should overwrite + const secondToken = "token-for-url2" + err = backend.Write(srvURL2, secondToken) + require.NoError(t, err) + + // Should have the second token (File backend doesn't differentiate by URL) + token, err := backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, secondToken, token) + }) +} diff --git a/cli/sessionstore/sessionstore_windows.go b/cli/sessionstore/sessionstore_windows.go new file mode 100644 index 0000000000000..6c48db0154f3f --- /dev/null +++ b/cli/sessionstore/sessionstore_windows.go @@ -0,0 +1,66 @@ +//go:build windows + +package sessionstore + +import ( + "errors" + "os" + "syscall" + + "github.com/danieljoos/wincred" +) + +const ( + // defaultServiceName is the service name used in the Windows Credential Manager + // for storing Coder CLI session tokens. + defaultServiceName = "coder-v2-credentials" +) + +// operatingSystemKeyring implements keyringProvider and uses Windows Credential Manager. +// It is largely adapted from the zalando/go-keyring package. +type operatingSystemKeyring struct{} + +func (operatingSystemKeyring) Set(service, credential string) error { + // password may not exceed 2560 bytes (https://github.com/jaraco/keyring/issues/540#issuecomment-968329967) + if len(credential) > 2560 { + return ErrSetDataTooBig + } + + // service may not exceed 512 bytes (might need more testing) + if len(service) >= 512 { + return ErrSetDataTooBig + } + + // service may not exceed 32k but problems occur before that + // so we limit it to 30k + if len(service) > 1024*30 { + return ErrSetDataTooBig + } + + cred := wincred.NewGenericCredential(service) + cred.CredentialBlob = []byte(credential) + cred.Persist = wincred.PersistLocalMachine + return cred.Write() +} + +func (operatingSystemKeyring) Get(service string) ([]byte, error) { + cred, err := wincred.GetGenericCredential(service) + if err != nil { + if errors.Is(err, syscall.ERROR_NOT_FOUND) { + return nil, os.ErrNotExist + } + return nil, err + } + return cred.CredentialBlob, nil +} + +func (operatingSystemKeyring) Delete(service string) error { + cred, err := wincred.GetGenericCredential(service) + if err != nil { + if errors.Is(err, syscall.ERROR_NOT_FOUND) { + return os.ErrNotExist + } + return err + } + return cred.Delete() +} diff --git a/cli/sessionstore/sessionstore_windows_test.go b/cli/sessionstore/sessionstore_windows_test.go new file mode 100644 index 0000000000000..4d90e27f5c132 --- /dev/null +++ b/cli/sessionstore/sessionstore_windows_test.go @@ -0,0 +1,127 @@ +//go:build windows + +package sessionstore_test + +import ( + "encoding/json" + "net/url" + "os" + "testing" + + "github.com/danieljoos/wincred" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/cli/sessionstore" +) + +func TestWindowsKeyring_WriteReadDelete(t *testing.T) { + t.Parallel() + + const testURL = "http://127.0.0.1:1337" + srvURL, err := url.Parse(testURL) + require.NoError(t, err) + + serviceName := keyringTestServiceName(t) + backend := sessionstore.NewKeyringWithService(serviceName) + t.Cleanup(func() { _ = backend.Delete(srvURL) }) + + // Verify no token exists initially + _, err = backend.Read(srvURL) + require.ErrorIs(t, err, os.ErrNotExist) + + // Write a token + const inputToken = "test-token-12345" + err = backend.Write(srvURL, inputToken) + require.NoError(t, err) + + // Verify the credential is stored in Windows Credential Manager with correct format + winCred, err := wincred.GetGenericCredential(serviceName) + require.NoError(t, err, "getting windows credential") + + var storedCreds map[string]struct { + CoderURL string `json:"coder_url"` + APIToken string `json:"api_token"` + } + err = json.Unmarshal(winCred.CredentialBlob, &storedCreds) + require.NoError(t, err, "unmarshalling stored credentials") + + // Verify the stored values + require.Len(t, storedCreds, 1) + cred, ok := storedCreds[srvURL.Host] + require.True(t, ok, "credential for URL should exist") + require.Equal(t, inputToken, cred.APIToken) + require.Equal(t, srvURL.Host, cred.CoderURL) + + // Read the token back + token, err := backend.Read(srvURL) + require.NoError(t, err) + require.Equal(t, inputToken, token) + + // Delete the token + err = backend.Delete(srvURL) + require.NoError(t, err) + + // Verify token is deleted + _, err = backend.Read(srvURL) + require.ErrorIs(t, err, os.ErrNotExist) +} + +func TestWindowsKeyring_MultipleServers(t *testing.T) { + t.Parallel() + + const testURL1 = "http://127.0.0.1:1337" + srv1URL, err := url.Parse(testURL1) + require.NoError(t, err) + + const testURL2 = "http://127.0.0.1:1338" + srv2URL, err := url.Parse(testURL2) + require.NoError(t, err) + + serviceName := keyringTestServiceName(t) + backend := sessionstore.NewKeyringWithService(serviceName) + t.Cleanup(func() { + _ = backend.Delete(srv1URL) + _ = backend.Delete(srv2URL) + }) + + // Write token for server 1 + const token1 = "token-server-1" + err = backend.Write(srv1URL, token1) + require.NoError(t, err) + + // Write token for server 2 (should NOT overwrite server 1's token) + const token2 = "token-server-2" + err = backend.Write(srv2URL, token2) + require.NoError(t, err) + + // Verify both credentials are stored in Windows Credential Manager + winCred, err := wincred.GetGenericCredential(serviceName) + require.NoError(t, err, "getting windows credential") + + var storedCreds map[string]struct { + CoderURL string `json:"coder_url"` + APIToken string `json:"api_token"` + } + err = json.Unmarshal(winCred.CredentialBlob, &storedCreds) + require.NoError(t, err, "unmarshalling stored credentials") + + // Both credentials should exist + require.Len(t, storedCreds, 2) + require.Equal(t, token1, storedCreds[srv1URL.Host].APIToken) + require.Equal(t, token2, storedCreds[srv2URL.Host].APIToken) + + // Read individual credentials + token, err := backend.Read(srv1URL) + require.NoError(t, err) + require.Equal(t, token1, token) + + token, err = backend.Read(srv2URL) + require.NoError(t, err) + require.Equal(t, token2, token) + + // Cleanup + err = backend.Delete(srv1URL) + require.NoError(t, err) + err = backend.Delete(srv2URL) + require.NoError(t, err) +} diff --git a/cli/testdata/coder_--help.golden b/cli/testdata/coder_--help.golden index 09dd4c3bce3a5..e457aca857070 100644 --- a/cli/testdata/coder_--help.golden +++ b/cli/testdata/coder_--help.golden @@ -108,6 +108,11 @@ variables or flags. --url url, $CODER_URL URL to a deployment. + --use-keyring bool, $CODER_USE_KEYRING + Store and retrieve session tokens using the operating system keyring. + Currently only supported on Windows. By default, tokens are stored in + plain text files. + -v, --verbose bool, $CODER_VERBOSE Enable verbose output. diff --git a/cli/testdata/coder_login_--help.golden b/cli/testdata/coder_login_--help.golden index e4109a494ed39..8709f60987cc7 100644 --- a/cli/testdata/coder_login_--help.golden +++ b/cli/testdata/coder_login_--help.golden @@ -5,6 +5,10 @@ USAGE: Authenticate with Coder deployment + By default, the session token is stored in a plain text file. Use the + --use-keyring flag or set CODER_USE_KEYRING=true to store the token in the + operating system keyring instead. + OPTIONS: --first-user-email string, $CODER_FIRST_USER_EMAIL Specifies an email address to use if creating the first user for the diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md index c1410b4599977..1005da991dc4f 100644 --- a/docs/reference/cli/index.md +++ b/docs/reference/cli/index.md @@ -170,6 +170,15 @@ Disable direct (P2P) connections to workspaces. Disable network telemetry. Network telemetry is collected when connecting to workspaces using the CLI, and is forwarded to the server. If telemetry is also enabled on the server, it may be sent to Coder. Network telemetry is used to measure network quality and detect regressions. +### --use-keyring + +| | | +|-------------|---------------------------------| +| Type | bool | +| Environment | $CODER_USE_KEYRING | + +Store and retrieve session tokens using the operating system keyring. Currently only supported on Windows. By default, tokens are stored in plain text files. + ### --global-config | | | diff --git a/docs/reference/cli/login.md b/docs/reference/cli/login.md index a35038fedef8c..459a332e06270 100644 --- a/docs/reference/cli/login.md +++ b/docs/reference/cli/login.md @@ -9,6 +9,12 @@ Authenticate with Coder deployment coder login [flags] [] ``` +## Description + +```console +By default, the session token is stored in a plain text file. Use the --use-keyring flag or set CODER_USE_KEYRING=true to store the token in the operating system keyring instead. +``` + ## Options ### --first-user-email diff --git a/enterprise/cli/testdata/coder_--help.golden b/enterprise/cli/testdata/coder_--help.golden index 8424ccac923a2..51ee58258f8e4 100644 --- a/enterprise/cli/testdata/coder_--help.golden +++ b/enterprise/cli/testdata/coder_--help.golden @@ -68,6 +68,11 @@ variables or flags. --url url, $CODER_URL URL to a deployment. + --use-keyring bool, $CODER_USE_KEYRING + Store and retrieve session tokens using the operating system keyring. + Currently only supported on Windows. By default, tokens are stored in + plain text files. + -v, --verbose bool, $CODER_VERBOSE Enable verbose output. diff --git a/go.mod b/go.mod index d87c8300234c8..b48dbb2ff683b 100644 --- a/go.mod +++ b/go.mod @@ -480,6 +480,7 @@ require ( github.com/coder/aisdk-go v0.0.9 github.com/coder/boundary v1.0.1-0.20250925154134-55a44f2a7945 github.com/coder/preview v1.0.4 + github.com/danieljoos/wincred v1.2.3 github.com/dgraph-io/ristretto/v2 v2.3.0 github.com/fsnotify/fsnotify v1.9.0 github.com/go-git/go-git/v5 v5.16.2 diff --git a/go.sum b/go.sum index 2655cb93f2cbd..94c7934bbb570 100644 --- a/go.sum +++ b/go.sum @@ -987,6 +987,8 @@ github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= +github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/dave/dst v0.27.2 h1:4Y5VFTkhGLC1oddtNwuxxe36pnyLxMFXT51FOzH8Ekc= github.com/dave/dst v0.27.2/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= github.com/dave/jennifer v1.6.1 h1:T4T/67t6RAA5AIV6+NP8Uk/BIsXgDoqEowgycdQQLuk= From 9c47733e16575722372669339c1bf387afca4ec4 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:26:12 +1100 Subject: [PATCH 039/255] ci: allow more time for gen & fmt jobs to be acquired (#20577) Closes https://github.com/coder/internal/issues/1081 The time taken for a runner to acquire a job counts towards the job-wide `timeout-minutes`. Recently we've been seeing outages in the runner infrastructure lead to a ~5-6m runner start time, causing fmt & gen jobs to be cancelled due to their 7 or 8 minute timeouts. This PR extends the job-wide timeout on fmt & gen to 20 minutes, but adds the original 7 and 8 minute timeouts to the `make [fmt|gen]` portion of the job -- the part of the job we have the most control over, and that we want to be made aware of timeouts for. --- .github/workflows/ci.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 96d0ce23953cf..50e7d2d542bdf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -230,7 +230,7 @@ jobs: shell: bash gen: - timeout-minutes: 8 + timeout-minutes: 20 runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }} if: ${{ !cancelled() }} steps: @@ -271,6 +271,7 @@ jobs: popd - name: make gen + timeout-minutes: 8 run: | # Remove golden files to detect discrepancy in generated files. make clean/golden-files @@ -288,7 +289,7 @@ jobs: needs: changes if: needs.changes.outputs.offlinedocs-only == 'false' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main' runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }} - timeout-minutes: 7 + timeout-minutes: 20 steps: - name: Harden Runner uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 @@ -315,6 +316,7 @@ jobs: run: go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0 - name: make fmt + timeout-minutes: 7 run: | PATH="${PATH}:$(go env GOPATH)/bin" \ make --output-sync -j -B fmt From 37222199c3d687f4d26bfc61d53fa4fd3537602d Mon Sep 17 00:00:00 2001 From: david-fraley <67079030+david-fraley@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:25:01 -0500 Subject: [PATCH 040/255] docs: update release calendar for 2.27.3 patch (#20597) --- docs/install/kubernetes.md | 4 ++-- docs/install/rancher.md | 2 +- docs/install/releases/index.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/install/kubernetes.md b/docs/install/kubernetes.md index 3af2d917b431d..ab6b79f93e1d4 100644 --- a/docs/install/kubernetes.md +++ b/docs/install/kubernetes.md @@ -136,7 +136,7 @@ We support two release channels: mainline and stable - read the helm install coder coder-v2/coder \ --namespace coder \ --values values.yaml \ - --version 2.27.1 + --version 2.27.3 ``` - **OCI Registry** @@ -147,7 +147,7 @@ We support two release channels: mainline and stable - read the helm install coder oci://ghcr.io/coder/chart/coder \ --namespace coder \ --values values.yaml \ - --version 2.27.2 + --version 2.27.3 ``` - **Stable** Coder release: diff --git a/docs/install/rancher.md b/docs/install/rancher.md index aaf9c947dff37..78d272befe944 100644 --- a/docs/install/rancher.md +++ b/docs/install/rancher.md @@ -134,7 +134,7 @@ kubectl create secret generic coder-db-url -n coder \ 1. Select a Coder version: - - **Mainline**: `2.27.2` + - **Mainline**: `2.27.3` - **Stable**: `2.26.3` Learn more about release channels in the [Releases documentation](./releases/index.md). diff --git a/docs/install/releases/index.md b/docs/install/releases/index.md index afe0cf4e1656c..e0539f4d9c0ee 100644 --- a/docs/install/releases/index.md +++ b/docs/install/releases/index.md @@ -62,7 +62,7 @@ pages. | [2.24](https://coder.com/changelog/coder-2-24) | July 01, 2025 | Not Supported | [v2.24.4](https://github.com/coder/coder/releases/tag/v2.24.4) | | [2.25](https://coder.com/changelog/coder-2-25) | August 05, 2025 | Security Support | [v2.25.3](https://github.com/coder/coder/releases/tag/v2.25.3) | | [2.26](https://coder.com/changelog/coder-2-26) | September 03, 2025 | Stable | [v2.26.3](https://github.com/coder/coder/releases/tag/v2.26.3) | -| [2.27](https://coder.com/changelog/coder-2-27) | October 02, 2025 | Mainline | [v2.27.2](https://github.com/coder/coder/releases/tag/v2.27.2) | +| [2.27](https://coder.com/changelog/coder-2-27) | October 02, 2025 | Mainline | [v2.27.3](https://github.com/coder/coder/releases/tag/v2.27.3) | | 2.28 | | Not Released | N/A | From 7182c53df7648cd7db8551629226c4a0a1cc8559 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Fri, 31 Oct 2025 13:40:56 +1100 Subject: [PATCH 041/255] chore: remove brazil fly.io proxy (#20601) --- .github/fly-wsproxies/sao-paulo-coder.toml | 34 ---------------------- .github/workflows/deploy.yaml | 2 -- 2 files changed, 36 deletions(-) delete mode 100644 .github/fly-wsproxies/sao-paulo-coder.toml diff --git a/.github/fly-wsproxies/sao-paulo-coder.toml b/.github/fly-wsproxies/sao-paulo-coder.toml deleted file mode 100644 index b6c9b964631ef..0000000000000 --- a/.github/fly-wsproxies/sao-paulo-coder.toml +++ /dev/null @@ -1,34 +0,0 @@ -app = "sao-paulo-coder" -primary_region = "gru" - -[experimental] - entrypoint = ["/bin/sh", "-c", "CODER_DERP_SERVER_RELAY_URL=\"http://[${FLY_PRIVATE_IP}]:3000\" /opt/coder wsproxy server"] - auto_rollback = true - -[build] - image = "ghcr.io/coder/coder-preview:main" - -[env] - CODER_ACCESS_URL = "https://sao-paulo.fly.dev.coder.com" - CODER_HTTP_ADDRESS = "0.0.0.0:3000" - CODER_PRIMARY_ACCESS_URL = "https://dev.coder.com" - CODER_WILDCARD_ACCESS_URL = "*--apps.sao-paulo.fly.dev.coder.com" - CODER_VERBOSE = "true" - -[http_service] - internal_port = 3000 - force_https = true - auto_stop_machines = true - auto_start_machines = true - min_machines_running = 0 - -# Ref: https://fly.io/docs/reference/configuration/#http_service-concurrency -[http_service.concurrency] - type = "requests" - soft_limit = 50 - hard_limit = 100 - -[[vm]] - cpu_kind = "shared" - cpus = 2 - memory_mb = 512 diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 30d9e384149fa..6ea750a11caac 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -163,12 +163,10 @@ jobs: run: | flyctl deploy --image "$IMAGE" --app paris-coder --config ./.github/fly-wsproxies/paris-coder.toml --env "CODER_PROXY_SESSION_TOKEN=$TOKEN_PARIS" --yes flyctl deploy --image "$IMAGE" --app sydney-coder --config ./.github/fly-wsproxies/sydney-coder.toml --env "CODER_PROXY_SESSION_TOKEN=$TOKEN_SYDNEY" --yes - flyctl deploy --image "$IMAGE" --app sao-paulo-coder --config ./.github/fly-wsproxies/sao-paulo-coder.toml --env "CODER_PROXY_SESSION_TOKEN=$TOKEN_SAO_PAULO" --yes flyctl deploy --image "$IMAGE" --app jnb-coder --config ./.github/fly-wsproxies/jnb-coder.toml --env "CODER_PROXY_SESSION_TOKEN=$TOKEN_JNB" --yes env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} IMAGE: ${{ inputs.image }} TOKEN_PARIS: ${{ secrets.FLY_PARIS_CODER_PROXY_SESSION_TOKEN }} TOKEN_SYDNEY: ${{ secrets.FLY_SYDNEY_CODER_PROXY_SESSION_TOKEN }} - TOKEN_SAO_PAULO: ${{ secrets.FLY_SAO_PAULO_CODER_PROXY_SESSION_TOKEN }} TOKEN_JNB: ${{ secrets.FLY_JNB_CODER_PROXY_SESSION_TOKEN }} From 9298e7e073970011f7711cf37ffce5c5defe1a8d Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Fri, 31 Oct 2025 14:55:14 +1100 Subject: [PATCH 042/255] chore: remove `@emotion/react` from `<404Page />` (#20530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR removes the dependency on `@emotion/react` from the 404 page as part of our ongoing effort to eliminate `@mui/` dependencies across the codebase. I’m slightly concerned that these Tailwind classes might not apply correctly if Tailwind fails to load. If thats an issue, it might be worth converting this page to raw CSS. --- site/src/pages/404Page/404Page.tsx | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/site/src/pages/404Page/404Page.tsx b/site/src/pages/404Page/404Page.tsx index b3be31f270118..a04b1d95a5670 100644 --- a/site/src/pages/404Page/404Page.tsx +++ b/site/src/pages/404Page/404Page.tsx @@ -2,26 +2,11 @@ import type { FC } from "react"; const NotFoundPage: FC = () => { return ( -
-
({ - margin: 8, - padding: 8, - borderRight: theme.palette.divider, - })} - > -

404

-
-

This page could not be found.

+
+

+ 404 + This page could not be found. +

); }; From 0f8f67ec6f2aba3a70d7345852f2c8d34f848af1 Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Fri, 31 Oct 2025 15:34:51 +1100 Subject: [PATCH 043/255] chore: update paywall grammar mistake (#20602) Resolved issue from #20331 where the article `an` was incorrectly used before words not beginning with a vowel sound. Updated affected instances to use the correct article `a`. ```diff - You need an Premium license to use this feature. + You need a Premium license to use this feature. ``` This was already correct on the following pages. * [`ConnectionLogPageView.tsx`](https://github.com/coder/coder/blob/7182c53df7648cd7db8551629226c4a0a1cc8559/site/src/pages/ConnectionLogPage/ConnectionLogPageView.tsx#L130) * [`GroupsPageView.tsx`](https://github.com/coder/coder/blob/7182c53df7648cd7db8551629226c4a0a1cc8559/site/src/pages/GroupsPage/GroupsPageView.tsx#L49) --- site/src/pages/AuditPage/AuditPageView.tsx | 2 +- .../DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx | 2 +- .../pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx | 2 +- .../TemplatePermissionsPage/TemplatePermissionsPage.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/site/src/pages/AuditPage/AuditPageView.tsx b/site/src/pages/AuditPage/AuditPageView.tsx index 080c3a0b2aaab..05eb6e03b735a 100644 --- a/site/src/pages/AuditPage/AuditPageView.tsx +++ b/site/src/pages/AuditPage/AuditPageView.tsx @@ -132,7 +132,7 @@ export const AuditPageView: FC = ({ diff --git a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx index 838685443f185..e66b038a93553 100644 --- a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/IdpOrgSyncPage.tsx @@ -80,7 +80,7 @@ const IdpOrgSyncPage: FC = () => { diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx index 64365f94bb44f..e9724bcb953f1 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPage.tsx @@ -132,7 +132,7 @@ const IdpSyncPage: FC = () => { diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx index ffc69bad45e03..f06885b57958e 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPage.tsx @@ -30,7 +30,7 @@ const TemplatePermissionsPage: FC = () => { {!isTemplateRBACEnabled ? ( ) : ( From 8f78baddb1f656134a030fc24a5a7eb2121318c1 Mon Sep 17 00:00:00 2001 From: Kacper Sawicki Date: Fri, 31 Oct 2025 09:43:06 +0100 Subject: [PATCH 044/255] feat(scaletest): switch notification trigger from creating a user to template deletion (#20512) This PR refactors the notification scale test to use template admins and template deletion as the notification trigger. Additionally, I've added a configurable timeout for SMTP requests. Previously, notifications were triggered by creating/deleting a user, and notifications were received by users with the owner role. However, because of how many notifications were generated by the runners, we had too many notifications to reliably test notification delivery. --- cli/exp_scaletest_notifications.go | 129 ++++++++++++++++------------ scaletest/notifications/config.go | 7 ++ scaletest/notifications/metrics.go | 6 ++ scaletest/notifications/run.go | 2 +- scaletest/notifications/run_test.go | 1 + 5 files changed, 91 insertions(+), 54 deletions(-) diff --git a/cli/exp_scaletest_notifications.go b/cli/exp_scaletest_notifications.go index 1ea47858933f1..0a9eb22d5c2e3 100644 --- a/cli/exp_scaletest_notifications.go +++ b/cli/exp_scaletest_notifications.go @@ -3,6 +3,7 @@ package cli import ( + "bytes" "context" "fmt" "net/http" @@ -29,12 +30,13 @@ import ( func (r *RootCmd) scaletestNotifications() *serpent.Command { var ( - userCount int64 - ownerUserPercentage float64 - notificationTimeout time.Duration - dialTimeout time.Duration - noCleanup bool - smtpAPIURL string + userCount int64 + templateAdminPercentage float64 + notificationTimeout time.Duration + smtpRequestTimeout time.Duration + dialTimeout time.Duration + noCleanup bool + smtpAPIURL string tracingFlags = &scaletestTracingFlags{} @@ -77,24 +79,24 @@ func (r *RootCmd) scaletestNotifications() *serpent.Command { return xerrors.Errorf("--user-count must be greater than 0") } - if ownerUserPercentage < 0 || ownerUserPercentage > 100 { - return xerrors.Errorf("--owner-user-percentage must be between 0 and 100") + if templateAdminPercentage < 0 || templateAdminPercentage > 100 { + return xerrors.Errorf("--template-admin-percentage must be between 0 and 100") } if smtpAPIURL != "" && !strings.HasPrefix(smtpAPIURL, "http://") && !strings.HasPrefix(smtpAPIURL, "https://") { return xerrors.Errorf("--smtp-api-url must start with http:// or https://") } - ownerUserCount := int64(float64(userCount) * ownerUserPercentage / 100) - if ownerUserCount == 0 && ownerUserPercentage > 0 { - ownerUserCount = 1 + templateAdminCount := int64(float64(userCount) * templateAdminPercentage / 100) + if templateAdminCount == 0 && templateAdminPercentage > 0 { + templateAdminCount = 1 } - regularUserCount := userCount - ownerUserCount + regularUserCount := userCount - templateAdminCount _, _ = fmt.Fprintf(inv.Stderr, "Distribution plan:\n") _, _ = fmt.Fprintf(inv.Stderr, " Total users: %d\n", userCount) - _, _ = fmt.Fprintf(inv.Stderr, " Owner users: %d (%.1f%%)\n", ownerUserCount, ownerUserPercentage) - _, _ = fmt.Fprintf(inv.Stderr, " Regular users: %d (%.1f%%)\n", regularUserCount, 100.0-ownerUserPercentage) + _, _ = fmt.Fprintf(inv.Stderr, " Template admins: %d (%.1f%%)\n", templateAdminCount, templateAdminPercentage) + _, _ = fmt.Fprintf(inv.Stderr, " Regular users: %d (%.1f%%)\n", regularUserCount, 100.0-templateAdminPercentage) outputs, err := output.parse() if err != nil { @@ -127,13 +129,12 @@ func (r *RootCmd) scaletestNotifications() *serpent.Command { _, _ = fmt.Fprintln(inv.Stderr, "Creating users...") dialBarrier := &sync.WaitGroup{} - ownerWatchBarrier := &sync.WaitGroup{} + templateAdminWatchBarrier := &sync.WaitGroup{} dialBarrier.Add(int(userCount)) - ownerWatchBarrier.Add(int(ownerUserCount)) + templateAdminWatchBarrier.Add(int(templateAdminCount)) expectedNotificationIDs := map[uuid.UUID]struct{}{ - notificationsLib.TemplateUserAccountCreated: {}, - notificationsLib.TemplateUserAccountDeleted: {}, + notificationsLib.TemplateTemplateDeleted: {}, } triggerTimes := make(map[uuid.UUID]chan time.Time, len(expectedNotificationIDs)) @@ -142,19 +143,20 @@ func (r *RootCmd) scaletestNotifications() *serpent.Command { } configs := make([]notifications.Config, 0, userCount) - for range ownerUserCount { + for range templateAdminCount { config := notifications.Config{ User: createusers.Config{ OrganizationID: me.OrganizationIDs[0], }, - Roles: []string{codersdk.RoleOwner}, + Roles: []string{codersdk.RoleTemplateAdmin}, NotificationTimeout: notificationTimeout, DialTimeout: dialTimeout, DialBarrier: dialBarrier, - ReceivingWatchBarrier: ownerWatchBarrier, + ReceivingWatchBarrier: templateAdminWatchBarrier, ExpectedNotificationsIDs: expectedNotificationIDs, Metrics: metrics, SMTPApiURL: smtpAPIURL, + SMTPRequestTimeout: smtpRequestTimeout, } if err := config.Validate(); err != nil { return xerrors.Errorf("validate config: %w", err) @@ -170,9 +172,8 @@ func (r *RootCmd) scaletestNotifications() *serpent.Command { NotificationTimeout: notificationTimeout, DialTimeout: dialTimeout, DialBarrier: dialBarrier, - ReceivingWatchBarrier: ownerWatchBarrier, + ReceivingWatchBarrier: templateAdminWatchBarrier, Metrics: metrics, - SMTPApiURL: smtpAPIURL, } if err := config.Validate(); err != nil { return xerrors.Errorf("validate config: %w", err) @@ -180,7 +181,7 @@ func (r *RootCmd) scaletestNotifications() *serpent.Command { configs = append(configs, config) } - go triggerUserNotifications( + go triggerNotifications( ctx, logger, client, @@ -261,23 +262,30 @@ func (r *RootCmd) scaletestNotifications() *serpent.Command { Required: true, }, { - Flag: "owner-user-percentage", - Env: "CODER_SCALETEST_NOTIFICATION_OWNER_USER_PERCENTAGE", + Flag: "template-admin-percentage", + Env: "CODER_SCALETEST_NOTIFICATION_TEMPLATE_ADMIN_PERCENTAGE", Default: "20.0", - Description: "Percentage of users to assign Owner role to (0-100).", - Value: serpent.Float64Of(&ownerUserPercentage), + Description: "Percentage of users to assign Template Admin role to (0-100).", + Value: serpent.Float64Of(&templateAdminPercentage), }, { Flag: "notification-timeout", Env: "CODER_SCALETEST_NOTIFICATION_TIMEOUT", - Default: "5m", + Default: "10m", Description: "How long to wait for notifications after triggering.", Value: serpent.DurationOf(¬ificationTimeout), }, + { + Flag: "smtp-request-timeout", + Env: "CODER_SCALETEST_SMTP_REQUEST_TIMEOUT", + Default: "5m", + Description: "Timeout for SMTP requests.", + Value: serpent.DurationOf(&smtpRequestTimeout), + }, { Flag: "dial-timeout", Env: "CODER_SCALETEST_DIAL_TIMEOUT", - Default: "2m", + Default: "10m", Description: "Timeout for dialing the notification websocket endpoint.", Value: serpent.DurationOf(&dialTimeout), }, @@ -379,9 +387,9 @@ func computeNotificationLatencies( return nil } -// triggerUserNotifications waits for all test users to connect, -// then creates and deletes a test user to trigger notification events for testing. -func triggerUserNotifications( +// triggerNotifications waits for all test users to connect, +// then creates and deletes a test template to trigger notification events for testing. +func triggerNotifications( ctx context.Context, logger slog.Logger, client *codersdk.Client, @@ -414,34 +422,49 @@ func triggerUserNotifications( return } - const ( - triggerUsername = "scaletest-trigger-user" - triggerEmail = "scaletest-trigger@example.com" - ) + logger.Info(ctx, "creating test template to test notifications") + + // Upload empty template file. + file, err := client.Upload(ctx, codersdk.ContentTypeTar, bytes.NewReader([]byte{})) + if err != nil { + logger.Error(ctx, "upload test template", slog.Error(err)) + return + } + logger.Info(ctx, "test template uploaded", slog.F("file_id", file.ID)) - logger.Info(ctx, "creating test user to test notifications", - slog.F("username", triggerUsername), - slog.F("email", triggerEmail), - slog.F("org_id", orgID)) + // Create template version. + version, err := client.CreateTemplateVersion(ctx, orgID, codersdk.CreateTemplateVersionRequest{ + StorageMethod: codersdk.ProvisionerStorageMethodFile, + FileID: file.ID, + Provisioner: codersdk.ProvisionerTypeEcho, + }) + if err != nil { + logger.Error(ctx, "create test template version", slog.Error(err)) + return + } + logger.Info(ctx, "test template version created", slog.F("template_version_id", version.ID)) - testUser, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ - OrganizationIDs: []uuid.UUID{orgID}, - Username: triggerUsername, - Email: triggerEmail, - Password: "test-password-123", + // Create template. + testTemplate, err := client.CreateTemplate(ctx, orgID, codersdk.CreateTemplateRequest{ + Name: "scaletest-test-template", + Description: "scaletest-test-template", + VersionID: version.ID, }) if err != nil { - logger.Error(ctx, "create test user", slog.Error(err)) + logger.Error(ctx, "create test template", slog.Error(err)) return } - expectedNotifications[notificationsLib.TemplateUserAccountCreated] <- time.Now() + logger.Info(ctx, "test template created", slog.F("template_id", testTemplate.ID)) - err = client.DeleteUser(ctx, testUser.ID) + // Delete template to trigger notification. + err = client.DeleteTemplate(ctx, testTemplate.ID) if err != nil { - logger.Error(ctx, "delete test user", slog.Error(err)) + logger.Error(ctx, "delete test template", slog.Error(err)) return } - expectedNotifications[notificationsLib.TemplateUserAccountDeleted] <- time.Now() - close(expectedNotifications[notificationsLib.TemplateUserAccountCreated]) - close(expectedNotifications[notificationsLib.TemplateUserAccountDeleted]) + logger.Info(ctx, "test template deleted", slog.F("template_id", testTemplate.ID)) + + // Record expected notification. + expectedNotifications[notificationsLib.TemplateTemplateDeleted] <- time.Now() + close(expectedNotifications[notificationsLib.TemplateTemplateDeleted]) } diff --git a/scaletest/notifications/config.go b/scaletest/notifications/config.go index ac8daeb9ef9cb..a3953eb5404a7 100644 --- a/scaletest/notifications/config.go +++ b/scaletest/notifications/config.go @@ -37,6 +37,9 @@ type Config struct { // SMTPApiUrl is the URL of the SMTP mock HTTP API SMTPApiURL string `json:"smtp_api_url"` + + // SMTPRequestTimeout is the timeout for SMTP requests. + SMTPRequestTimeout time.Duration `json:"smtp_request_timeout"` } func (c Config) Validate() error { @@ -61,6 +64,10 @@ func (c Config) Validate() error { return xerrors.New("notification_timeout must be greater than 0") } + if c.SMTPApiURL != "" && c.SMTPRequestTimeout <= 0 { + return xerrors.New("smtp_request_timeout must be set if smtp_api_url is set") + } + if c.DialTimeout <= 0 { return xerrors.New("dial_timeout must be greater than 0") } diff --git a/scaletest/notifications/metrics.go b/scaletest/notifications/metrics.go index 0bf3ebad74044..6d9c1a03fa956 100644 --- a/scaletest/notifications/metrics.go +++ b/scaletest/notifications/metrics.go @@ -28,6 +28,12 @@ func NewMetrics(reg prometheus.Registerer) *Metrics { Subsystem: "scaletest", Name: "notification_delivery_latency_seconds", Help: "Time between notification-creating action and receipt of notification by client", + Buckets: []float64{ + 1, 5, 10, 30, 60, + 120, 180, 240, 300, 360, 420, 480, 540, 600, 660, 720, 780, 840, 900, + 1200, 1500, 1800, 2100, 2400, 2700, 3000, 3300, 3600, 3900, 4200, 4500, + 5400, 7200, + }, }, []string{"notification_id", "notification_type"}) errors := prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "coderd", diff --git a/scaletest/notifications/run.go b/scaletest/notifications/run.go index abe844574659e..64ebe71235995 100644 --- a/scaletest/notifications/run.go +++ b/scaletest/notifications/run.go @@ -299,7 +299,7 @@ func (r *Runner) watchNotificationsSMTP(ctx context.Context, user codersdk.User, apiURL := fmt.Sprintf("%s/messages?email=%s", r.cfg.SMTPApiURL, user.Email) httpClient := &http.Client{ - Timeout: 10 * time.Second, + Timeout: r.cfg.SMTPRequestTimeout, } const smtpPollInterval = 2 * time.Second diff --git a/scaletest/notifications/run_test.go b/scaletest/notifications/run_test.go index 1e198e9edd91d..21c89a871440c 100644 --- a/scaletest/notifications/run_test.go +++ b/scaletest/notifications/run_test.go @@ -228,6 +228,7 @@ func TestRunWithSMTP(t *testing.T) { ReceivingWatchBarrier: receivingWatchBarrier, ExpectedNotificationsIDs: expectedNotificationsIDs, SMTPApiURL: smtpAPIServer.URL, + SMTPRequestTimeout: testutil.WaitLong, } err := runnerCfg.Validate() require.NoError(t, err) From 7b6e72438b697023109bedfa386d48b9472f0586 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Fri, 31 Oct 2025 15:36:08 +0400 Subject: [PATCH 045/255] chore: update quartz to 0.3.0 (#20604) Upgrade coder/quartz dep to 0.3.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b48dbb2ff683b..45c8189eada3d 100644 --- a/go.mod +++ b/go.mod @@ -98,7 +98,7 @@ require ( github.com/coder/flog v1.1.0 github.com/coder/guts v1.6.1 github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 - github.com/coder/quartz v0.2.1 + github.com/coder/quartz v0.3.0 github.com/coder/retry v1.5.1 github.com/coder/serpent v0.11.0 github.com/coder/terraform-provider-coder/v2 v2.12.0 diff --git a/go.sum b/go.sum index 94c7934bbb570..615b1ecbe5ec8 100644 --- a/go.sum +++ b/go.sum @@ -942,8 +942,8 @@ github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= github.com/coder/preview v1.0.4 h1:f506bnyhHtI3ICl/8Eb/gemcKvm/AGzQ91uyxjF+D9k= github.com/coder/preview v1.0.4/go.mod h1:PpLayC3ngQQ0iUhW2yVRFszOooto4JrGGMomv1rqUvA= -github.com/coder/quartz v0.2.1 h1:QgQ2Vc1+mvzewg2uD/nj8MJ9p9gE+QhGJm+Z+NGnrSE= -github.com/coder/quartz v0.2.1/go.mod h1:vsiCc+AHViMKH2CQpGIpFgdHIEQsxwm8yCscqKmzbRA= +github.com/coder/quartz v0.3.0 h1:bUoSEJ77NBfKtUqv6CPSC0AS8dsjqAqqAv7bN02m1mg= +github.com/coder/quartz v0.3.0/go.mod h1:BgE7DOj/8NfvRgvKw0jPLDQH/2Lya2kxcTaNJ8X0rZk= github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc= github.com/coder/retry v1.5.1/go.mod h1:blHMk9vs6LkoRT9ZHyuZo360cufXEhrxqvEzeMtRGoY= github.com/coder/serpent v0.11.0 h1:VKIIbBg0ManopqqDsutBGf7YYTUXsPQgBx//m1SJQ90= From 7ae3fdc7496376291a86d684695460c69a206b2e Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 31 Oct 2025 15:53:27 +0200 Subject: [PATCH 046/255] refactor: use task data model for notifications (#20590) Updates coder/internal#973 Updates coder/internal#974 --- coderd/aitasks_test.go | 55 +++++------- coderd/database/queries.sql.go | 9 +- coderd/database/queries/workspaceagents.sql | 7 +- coderd/httpmw/workspaceagent.go | 1 + coderd/rbac/scopes.go | 14 ++- coderd/workspaceagents.go | 96 +++++++++------------ 6 files changed, 92 insertions(+), 90 deletions(-) diff --git a/coderd/aitasks_test.go b/coderd/aitasks_test.go index 4f645d34cf3bd..34f6dd4a0798c 100644 --- a/coderd/aitasks_test.go +++ b/coderd/aitasks_test.go @@ -2,15 +2,12 @@ package coderd_test import ( "context" - "database/sql" "encoding/json" "io" "net/http" "net/http/httptest" - "strings" "testing" "time" - "unicode/utf8" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -1325,31 +1322,31 @@ func TestTasksNotification(t *testing.T) { // Given: a workspace build with an agent containing an App workspaceAgentAppID := uuid.New() workspaceBuildID := uuid.New() - workspaceBuildSeed := database.WorkspaceBuild{ + workspaceBuilder := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ + OrganizationID: ownerUser.OrganizationID, + OwnerID: memberUser.ID, + }).Seed(database.WorkspaceBuild{ ID: workspaceBuildID, - } + }) if tc.isAITask { - workspaceBuildSeed = database.WorkspaceBuild{ - ID: workspaceBuildID, - // AI Task configuration - HasAITask: sql.NullBool{Bool: true, Valid: true}, - AITaskSidebarAppID: uuid.NullUUID{UUID: workspaceAgentAppID, Valid: true}, - } + workspaceBuilder = workspaceBuilder. + WithTask(database.TaskTable{ + Prompt: tc.taskPrompt, + }, &proto.App{ + Id: workspaceAgentAppID.String(), + Slug: "ccw", + }) + } else { + workspaceBuilder = workspaceBuilder. + WithAgent(func(agent []*proto.Agent) []*proto.Agent { + agent[0].Apps = []*proto.App{{ + Id: workspaceAgentAppID.String(), + Slug: "ccw", + }} + return agent + }) } - workspaceBuild := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{ - OrganizationID: ownerUser.OrganizationID, - OwnerID: memberUser.ID, - }).Seed(workspaceBuildSeed).Params(database.WorkspaceBuildParameter{ - WorkspaceBuildID: workspaceBuildID, - Name: codersdk.AITaskPromptParameterName, - Value: tc.taskPrompt, - }).WithAgent(func(agent []*proto.Agent) []*proto.Agent { - agent[0].Apps = []*proto.App{{ - Id: workspaceAgentAppID.String(), - Slug: "ccw", - }} - return agent - }).Do() + workspaceBuild := workspaceBuilder.Do() // Given: the workspace agent app has previous statuses agentClient := agentsdk.New(client.URL, agentsdk.WithFixedToken(workspaceBuild.AgentToken)) @@ -1390,13 +1387,7 @@ func TestTasksNotification(t *testing.T) { require.Len(t, sent, 1) require.Equal(t, memberUser.ID, sent[0].UserID) require.Len(t, sent[0].Labels, 2) - // NOTE: len(string) is the number of bytes in the string, not the number of runes. - require.LessOrEqual(t, utf8.RuneCountInString(sent[0].Labels["task"]), 160) - if len(tc.taskPrompt) > 160 { - require.Contains(t, tc.taskPrompt, strings.TrimSuffix(sent[0].Labels["task"], "…")) - } else { - require.Equal(t, tc.taskPrompt, sent[0].Labels["task"]) - } + require.Equal(t, workspaceBuild.Task.Name, sent[0].Labels["task"]) require.Equal(t, workspace.Name, sent[0].Labels["workspace"]) } else { // Then: No notification is sent diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e9108751cf4a5..65fac4733b100 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -17569,7 +17569,8 @@ const getWorkspaceAgentAndLatestBuildByAuthToken = `-- name: GetWorkspaceAgentAn SELECT workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.expanded_directory, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps, workspace_agents.api_version, workspace_agents.display_order, workspace_agents.parent_id, workspace_agents.api_key_scope, workspace_agents.deleted, - workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.provisioner_state, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.ai_task_sidebar_app_id, workspace_build_with_user.has_external_agent, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name + workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.provisioner_state, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.ai_task_sidebar_app_id, workspace_build_with_user.has_external_agent, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name, + tasks.id AS task_id FROM workspace_agents JOIN @@ -17584,6 +17585,10 @@ JOIN workspaces ON workspace_build_with_user.workspace_id = workspaces.id +LEFT JOIN + tasks +ON + tasks.workspace_id = workspaces.id WHERE -- This should only match 1 agent, so 1 returned row or 0. workspace_agents.auth_token = $1::uuid @@ -17607,6 +17612,7 @@ type GetWorkspaceAgentAndLatestBuildByAuthTokenRow struct { WorkspaceTable WorkspaceTable `db:"workspace_table" json:"workspace_table"` WorkspaceAgent WorkspaceAgent `db:"workspace_agent" json:"workspace_agent"` WorkspaceBuild WorkspaceBuild `db:"workspace_build" json:"workspace_build"` + TaskID uuid.NullUUID `db:"task_id" json:"task_id"` } func (q *sqlQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Context, authToken uuid.UUID) (GetWorkspaceAgentAndLatestBuildByAuthTokenRow, error) { @@ -17686,6 +17692,7 @@ func (q *sqlQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Cont &i.WorkspaceBuild.InitiatorByAvatarUrl, &i.WorkspaceBuild.InitiatorByUsername, &i.WorkspaceBuild.InitiatorByName, + &i.TaskID, ) return i, err } diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index cc59e96544778..b60d1f2c88455 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -285,7 +285,8 @@ WHERE SELECT sqlc.embed(workspaces), sqlc.embed(workspace_agents), - sqlc.embed(workspace_build_with_user) + sqlc.embed(workspace_build_with_user), + tasks.id AS task_id FROM workspace_agents JOIN @@ -300,6 +301,10 @@ JOIN workspaces ON workspace_build_with_user.workspace_id = workspaces.id +LEFT JOIN + tasks +ON + tasks.workspace_id = workspaces.id WHERE -- This should only match 1 agent, so 1 returned row or 0. workspace_agents.auth_token = @auth_token::uuid diff --git a/coderd/httpmw/workspaceagent.go b/coderd/httpmw/workspaceagent.go index 0ee231b2f5a12..d5f4e6fef21b6 100644 --- a/coderd/httpmw/workspaceagent.go +++ b/coderd/httpmw/workspaceagent.go @@ -118,6 +118,7 @@ func ExtractWorkspaceAgentAndLatestBuild(opts ExtractWorkspaceAgentAndLatestBuil OwnerID: row.WorkspaceTable.OwnerID, TemplateID: row.WorkspaceTable.TemplateID, VersionID: row.WorkspaceBuild.TemplateVersionID, + TaskID: row.TaskID, BlockUserData: row.WorkspaceAgent.APIKeyScope == database.AgentKeyScopeEnumNoUserData, }), ) diff --git a/coderd/rbac/scopes.go b/coderd/rbac/scopes.go index 5c8c80305679c..4e5babba29e0f 100644 --- a/coderd/rbac/scopes.go +++ b/coderd/rbac/scopes.go @@ -18,6 +18,7 @@ type WorkspaceAgentScopeParams struct { OwnerID uuid.UUID TemplateID uuid.UUID VersionID uuid.UUID + TaskID uuid.NullUUID BlockUserData bool } @@ -42,6 +43,15 @@ func WorkspaceAgentScope(params WorkspaceAgentScopeParams) Scope { panic("failed to expand scope, this should never happen") } + // Include task in the allow list if the workspace has an associated task. + var extraAllowList []AllowListElement + if params.TaskID.Valid { + extraAllowList = append(extraAllowList, AllowListElement{ + Type: ResourceTask.Type, + ID: params.TaskID.UUID.String(), + }) + } + return Scope{ // TODO: We want to limit the role too to be extra safe. // Even though the allowlist blocks anything else, it is still good @@ -52,12 +62,12 @@ func WorkspaceAgentScope(params WorkspaceAgentScopeParams) Scope { // Limit the agent to only be able to access the singular workspace and // the template/version it was created from. Add additional resources here // as needed, but do not add more workspace or template resource ids. - AllowIDList: []AllowListElement{ + AllowIDList: append([]AllowListElement{ {Type: ResourceWorkspace.Type, ID: params.WorkspaceID.String()}, {Type: ResourceTemplate.Type, ID: params.TemplateID.String()}, {Type: ResourceTemplate.Type, ID: params.VersionID.String()}, {Type: ResourceUser.Type, ID: params.OwnerID.String()}, - }, + }, extraAllowList...), } } diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 23046dab28e15..1374d92dc4d12 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -461,67 +461,55 @@ func (api *API) enqueueAITaskStateNotification( return } - workspaceBuild, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) - if err != nil { - api.Logger.Warn(ctx, "failed to get workspace build", slog.Error(err)) + if !workspace.TaskID.Valid { + // Workspace has no task ID, do nothing. return } - // Confirm Workspace Agent App is an AI Task - if workspaceBuild.HasAITask.Valid && workspaceBuild.HasAITask.Bool && - workspaceBuild.AITaskSidebarAppID.Valid && workspaceBuild.AITaskSidebarAppID.UUID == appID { - // Skip if the latest persisted state equals the new state (no new transition) - if len(latestAppStatus) > 0 && latestAppStatus[0].State == database.WorkspaceAppStatusState(newAppStatus) { - return - } + task, err := api.Database.GetTaskByID(ctx, workspace.TaskID.UUID) + if err != nil { + api.Logger.Warn(ctx, "failed to get task", slog.Error(err)) + return + } - // Skip the initial "Working" notification when task first starts. - // This is obvious to the user since they just created the task. - // We still notify on first "Idle" status and all subsequent transitions. - if len(latestAppStatus) == 0 && newAppStatus == codersdk.WorkspaceAppStatusStateWorking { - return - } + if !task.WorkspaceAppID.Valid || task.WorkspaceAppID.UUID != appID { + // Non-task app, do nothing. + return + } - // Use the task prompt as the "task" label, fallback to workspace name - parameters, err := api.Database.GetWorkspaceBuildParameters(ctx, workspaceBuild.ID) - if err != nil { - api.Logger.Warn(ctx, "failed to get workspace build parameters", slog.Error(err)) - return - } - taskName := workspace.Name - for _, param := range parameters { - if param.Name == codersdk.AITaskPromptParameterName { - taskName = param.Value - } - } + // Skip if the latest persisted state equals the new state (no new transition) + if len(latestAppStatus) > 0 && latestAppStatus[0].State == database.WorkspaceAppStatusState(newAppStatus) { + return + } - // As task prompt may be particularly long, truncate it to 160 characters for notifications. - if len(taskName) > 160 { - taskName = strutil.Truncate(taskName, 160, strutil.TruncateWithEllipsis, strutil.TruncateWithFullWords) - } + // Skip the initial "Working" notification when task first starts. + // This is obvious to the user since they just created the task. + // We still notify on first "Idle" status and all subsequent transitions. + if len(latestAppStatus) == 0 && newAppStatus == codersdk.WorkspaceAppStatusStateWorking { + return + } - if _, err := api.NotificationsEnqueuer.EnqueueWithData( - // nolint:gocritic // Need notifier actor to enqueue notifications - dbauthz.AsNotifier(ctx), - workspace.OwnerID, - notificationTemplate, - map[string]string{ - "task": taskName, - "workspace": workspace.Name, - }, - map[string]any{ - // Use a 1-minute bucketed timestamp to bypass per-day dedupe, - // allowing identical content to resend within the same day - // (but not more than once every 10s). - "dedupe_bypass_ts": api.Clock.Now().UTC().Truncate(time.Minute), - }, - "api-workspace-agent-app-status", - // Associate this notification with related entities - workspace.ID, workspace.OwnerID, workspace.OrganizationID, appID, - ); err != nil { - api.Logger.Warn(ctx, "failed to notify of task state", slog.Error(err)) - return - } + if _, err := api.NotificationsEnqueuer.EnqueueWithData( + // nolint:gocritic // Need notifier actor to enqueue notifications + dbauthz.AsNotifier(ctx), + workspace.OwnerID, + notificationTemplate, + map[string]string{ + "task": task.Name, + "workspace": workspace.Name, + }, + map[string]any{ + // Use a 1-minute bucketed timestamp to bypass per-day dedupe, + // allowing identical content to resend within the same day + // (but not more than once every 10s). + "dedupe_bypass_ts": api.Clock.Now().UTC().Truncate(time.Minute), + }, + "api-workspace-agent-app-status", + // Associate this notification with related entities + workspace.ID, workspace.OwnerID, workspace.OrganizationID, appID, + ); err != nil { + api.Logger.Warn(ctx, "failed to notify of task state", slog.Error(err)) + return } } From c627a68e96caca359bb406be61313c8790703bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B1=E3=82=A4=E3=83=A9?= Date: Fri, 31 Oct 2025 15:15:30 -0600 Subject: [PATCH 047/255] chore: migrate some tests from jest to vitest (#20568) --- site/.knip.jsonc | 5 +- site/jest.config.ts | 2 +- site/package.json | 14 +- site/pnpm-lock.yaml | 558 +++++++++++++++++- site/src/api/{api.test.ts => api.jest.ts} | 0 site/src/api/api.ts | 2 +- .../ConfirmDialog/ConfirmDialog.test.tsx | 6 +- .../DeleteDialog/DeleteDialog.test.tsx | 12 +- .../components/FileUpload/FileUpload.test.tsx | 6 +- .../components/GlobalSnackbar/utils.test.ts | 21 +- ...test.tsx => PaginationWidgetBase.jest.tsx} | 0 ...Context.test.tsx => ProxyContext.jest.tsx} | 0 site/src/contexts/auth/AuthProvider.test.tsx | 33 -- ...uireAuth.test.tsx => RequireAuth.jest.tsx} | 0 .../{debounce.test.ts => debounce.jest.ts} | 0 site/src/hooks/events.test.ts | 4 +- site/src/hooks/hookPolyfills.test.ts | 6 +- site/src/hooks/useClickable.test.tsx | 16 +- ...ipboard.test.tsx => useClipboard.jest.tsx} | 0 ...ta.test.ts => useEmbeddedMetadata.jest.ts} | 0 ...uery.test.ts => usePaginatedQuery.jest.ts} | 8 +- ...Key.test.ts => useSearchParamsKey.jest.ts} | 0 ...yout.test.tsx => DashboardLayout.jest.tsx} | 0 .../modules/dashboard/Navbar/Navbar.test.tsx | 64 -- .../dashboard/Navbar/NavbarView.test.tsx | 104 ---- .../UserDropdown/UserDropdownContent.test.tsx | 4 +- ...Check.test.tsx => useUpdateCheck.jest.tsx} | 0 .../resources/PortForwardPopoverView.test.tsx | 40 -- ...s.test.tsx => useAgentContainers.jest.tsx} | 0 ...AgentLogs.test.ts => useAgentLogs.jest.ts} | 0 ...cParameterFlowDeprecationWarning.jest.tsx} | 0 ...ter.test.tsx => DynamicParameter.jest.tsx} | 0 ...t.tsx => useWorkspaceDuplication.jest.tsx} | 0 ...{AuditPage.test.tsx => AuditPage.jest.tsx} | 0 ...ge.test.tsx => ConnectionLogPage.jest.tsx} | 0 .../CreateTemplateGalleryPage.test.tsx | 53 -- ...e.test.tsx => CreateTemplatePage.jest.tsx} | 0 ...Page.test.tsx => CreateTokenPage.jest.tsx} | 0 ...rPage.test.tsx => CreateUserPage.jest.tsx} | 0 ....test.tsx => CreateWorkspacePage.jest.tsx} | 0 ... CreateWorkspacePageExperimental.jest.tsx} | 0 ...{LoginPage.test.tsx => LoginPage.jest.tsx} | 0 ...t.tsx => OrganizationMembersPage.jest.tsx} | 0 ...test.tsx => OrganizationRedirect.jest.tsx} | 0 ...{SetupPage.test.tsx => SetupPage.jest.tsx} | 0 ...ge.test.tsx => TemplateEmbedPage.jest.tsx} | 0 ...ge.test.tsx => TemplateFilesPage.jest.tsx} | 0 ...sx => TemplateRedirectController.jest.tsx} | 0 ...test.ts => useDeletionDialogState.jest.ts} | 0 ...test.tsx => TemplateSettingsPage.jest.tsx} | 0 ...test.tsx => TemplateSchedulePage.jest.tsx} | 0 ...est.tsx => TemplateVariablesPage.jest.tsx} | 0 ...tsx => TemplateVersionEditorPage.jest.tsx} | 0 ....test.tsx => TemplateVersionPage.jest.tsx} | 0 ...nalPage.test.tsx => TerminalPage.jest.tsx} | 0 ...ountPage.test.tsx => AccountPage.jest.tsx} | 0 ...ePage.test.tsx => AppearancePage.jest.tsx} | 0 ...KeysPage.test.tsx => SSHKeysPage.jest.tsx} | 0 ...ulePage.test.tsx => SchedulePage.jest.tsx} | 0 ...ityPage.test.tsx => SecurityPage.jest.tsx} | 0 ...e.test.tsx => WorkspaceBuildPage.jest.tsx} | 0 ...cePage.test.tsx => WorkspacePage.jest.tsx} | 0 ...tsx => WorkspaceScheduleControls.jest.tsx} | 0 ...t.tsx => WorkspaceParametersPage.jest.tsx} | 0 ...est.tsx => WorkspaceScheduleForm.jest.tsx} | 9 +- ...est.tsx => WorkspaceSchedulePage.jest.tsx} | 0 ...est.tsx => WorkspaceSettingsPage.jest.tsx} | 0 ...sPage.test.tsx => WorkspacesPage.jest.tsx} | 0 site/src/testHelpers/entities.ts | 4 - ...{websockets.test.ts => websockets.jest.ts} | 0 ...Socket.test.ts => OneWayWebSocket.jest.ts} | 0 site/src/utils/events.test.ts | 18 +- .../{formUtils.test.ts => formUtils.jest.ts} | 0 site/src/utils/{tar.test.ts => tar.jest.ts} | 0 site/src/utils/tar.ts | 2 +- site/tsconfig.json | 2 +- site/vite.config.mts | 11 +- 77 files changed, 629 insertions(+), 375 deletions(-) rename site/src/api/{api.test.ts => api.jest.ts} (100%) rename site/src/components/PaginationWidget/{PaginationWidgetBase.test.tsx => PaginationWidgetBase.jest.tsx} (100%) rename site/src/contexts/{ProxyContext.test.tsx => ProxyContext.jest.tsx} (100%) delete mode 100644 site/src/contexts/auth/AuthProvider.test.tsx rename site/src/contexts/auth/{RequireAuth.test.tsx => RequireAuth.jest.tsx} (100%) rename site/src/hooks/{debounce.test.ts => debounce.jest.ts} (100%) rename site/src/hooks/{useClipboard.test.tsx => useClipboard.jest.tsx} (100%) rename site/src/hooks/{useEmbeddedMetadata.test.ts => useEmbeddedMetadata.jest.ts} (100%) rename site/src/hooks/{usePaginatedQuery.test.ts => usePaginatedQuery.jest.ts} (96%) rename site/src/hooks/{useSearchParamsKey.test.ts => useSearchParamsKey.jest.ts} (100%) rename site/src/modules/dashboard/{DashboardLayout.test.tsx => DashboardLayout.jest.tsx} (100%) delete mode 100644 site/src/modules/dashboard/Navbar/Navbar.test.tsx delete mode 100644 site/src/modules/dashboard/Navbar/NavbarView.test.tsx rename site/src/modules/dashboard/{useUpdateCheck.test.tsx => useUpdateCheck.jest.tsx} (100%) delete mode 100644 site/src/modules/resources/PortForwardPopoverView.test.tsx rename site/src/modules/resources/{useAgentContainers.test.tsx => useAgentContainers.jest.tsx} (100%) rename site/src/modules/resources/{useAgentLogs.test.ts => useAgentLogs.jest.ts} (100%) rename site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/{ClassicParameterFlowDeprecationWarning.test.tsx => ClassicParameterFlowDeprecationWarning.jest.tsx} (100%) rename site/src/modules/workspaces/DynamicParameter/{DynamicParameter.test.tsx => DynamicParameter.jest.tsx} (100%) rename site/src/modules/workspaces/WorkspaceMoreActions/{useWorkspaceDuplication.test.tsx => useWorkspaceDuplication.jest.tsx} (100%) rename site/src/pages/AuditPage/{AuditPage.test.tsx => AuditPage.jest.tsx} (100%) rename site/src/pages/ConnectionLogPage/{ConnectionLogPage.test.tsx => ConnectionLogPage.jest.tsx} (100%) delete mode 100644 site/src/pages/CreateTemplateGalleryPage/CreateTemplateGalleryPage.test.tsx rename site/src/pages/CreateTemplatePage/{CreateTemplatePage.test.tsx => CreateTemplatePage.jest.tsx} (100%) rename site/src/pages/CreateTokenPage/{CreateTokenPage.test.tsx => CreateTokenPage.jest.tsx} (100%) rename site/src/pages/CreateUserPage/{CreateUserPage.test.tsx => CreateUserPage.jest.tsx} (100%) rename site/src/pages/CreateWorkspacePage/{CreateWorkspacePage.test.tsx => CreateWorkspacePage.jest.tsx} (100%) rename site/src/pages/CreateWorkspacePage/{CreateWorkspacePageExperimental.test.tsx => CreateWorkspacePageExperimental.jest.tsx} (100%) rename site/src/pages/LoginPage/{LoginPage.test.tsx => LoginPage.jest.tsx} (100%) rename site/src/pages/OrganizationSettingsPage/{OrganizationMembersPage.test.tsx => OrganizationMembersPage.jest.tsx} (100%) rename site/src/pages/OrganizationSettingsPage/{OrganizationRedirect.test.tsx => OrganizationRedirect.jest.tsx} (100%) rename site/src/pages/SetupPage/{SetupPage.test.tsx => SetupPage.jest.tsx} (100%) rename site/src/pages/TemplatePage/TemplateEmbedPage/{TemplateEmbedPage.test.tsx => TemplateEmbedPage.jest.tsx} (100%) rename site/src/pages/TemplatePage/TemplateFilesPage/{TemplateFilesPage.test.tsx => TemplateFilesPage.jest.tsx} (100%) rename site/src/pages/TemplatePage/{TemplateRedirectController.test.tsx => TemplateRedirectController.jest.tsx} (100%) rename site/src/pages/TemplatePage/{useDeletionDialogState.test.ts => useDeletionDialogState.jest.ts} (100%) rename site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/{TemplateSettingsPage.test.tsx => TemplateSettingsPage.jest.tsx} (100%) rename site/src/pages/TemplateSettingsPage/TemplateSchedulePage/{TemplateSchedulePage.test.tsx => TemplateSchedulePage.jest.tsx} (100%) rename site/src/pages/TemplateSettingsPage/TemplateVariablesPage/{TemplateVariablesPage.test.tsx => TemplateVariablesPage.jest.tsx} (100%) rename site/src/pages/TemplateVersionEditorPage/{TemplateVersionEditorPage.test.tsx => TemplateVersionEditorPage.jest.tsx} (100%) rename site/src/pages/TemplateVersionPage/{TemplateVersionPage.test.tsx => TemplateVersionPage.jest.tsx} (100%) rename site/src/pages/TerminalPage/{TerminalPage.test.tsx => TerminalPage.jest.tsx} (100%) rename site/src/pages/UserSettingsPage/AccountPage/{AccountPage.test.tsx => AccountPage.jest.tsx} (100%) rename site/src/pages/UserSettingsPage/AppearancePage/{AppearancePage.test.tsx => AppearancePage.jest.tsx} (100%) rename site/src/pages/UserSettingsPage/SSHKeysPage/{SSHKeysPage.test.tsx => SSHKeysPage.jest.tsx} (100%) rename site/src/pages/UserSettingsPage/SchedulePage/{SchedulePage.test.tsx => SchedulePage.jest.tsx} (100%) rename site/src/pages/UserSettingsPage/SecurityPage/{SecurityPage.test.tsx => SecurityPage.jest.tsx} (100%) rename site/src/pages/WorkspaceBuildPage/{WorkspaceBuildPage.test.tsx => WorkspaceBuildPage.jest.tsx} (100%) rename site/src/pages/WorkspacePage/{WorkspacePage.test.tsx => WorkspacePage.jest.tsx} (100%) rename site/src/pages/WorkspacePage/{WorkspaceScheduleControls.test.tsx => WorkspaceScheduleControls.jest.tsx} (100%) rename site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/{WorkspaceParametersPage.test.tsx => WorkspaceParametersPage.jest.tsx} (100%) rename site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/{WorkspaceScheduleForm.test.tsx => WorkspaceScheduleForm.jest.tsx} (99%) rename site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/{WorkspaceSchedulePage.test.tsx => WorkspaceSchedulePage.jest.tsx} (100%) rename site/src/pages/WorkspaceSettingsPage/{WorkspaceSettingsPage.test.tsx => WorkspaceSettingsPage.jest.tsx} (100%) rename site/src/pages/WorkspacesPage/{WorkspacesPage.test.tsx => WorkspacesPage.jest.tsx} (100%) rename site/src/testHelpers/{websockets.test.ts => websockets.jest.ts} (100%) rename site/src/utils/{OneWayWebSocket.test.ts => OneWayWebSocket.jest.ts} (100%) rename site/src/utils/{formUtils.test.ts => formUtils.jest.ts} (100%) rename site/src/utils/{tar.test.ts => tar.jest.ts} (100%) diff --git a/site/.knip.jsonc b/site/.knip.jsonc index 1d620f9134781..312d4a9782ea0 100644 --- a/site/.knip.jsonc +++ b/site/.knip.jsonc @@ -8,5 +8,8 @@ "@types/react-virtualized-auto-sizer", "jest_workaround", "ts-proto" - ] + ], + "jest": { + "entry": "./src/**/*.jest.{ts,tsx}" + } } diff --git a/site/jest.config.ts b/site/jest.config.ts index 887b91fb9dee6..5ee9ec7ebd36b 100644 --- a/site/jest.config.ts +++ b/site/jest.config.ts @@ -31,7 +31,7 @@ module.exports = { testEnvironmentOptions: { customExportConditions: [""], }, - testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", + testRegex: "(/__tests__/.*|(\\.|/)(jest))\\.tsx?$", testPathIgnorePatterns: [ "/node_modules/", "/e2e/", diff --git a/site/package.json b/site/package.json index 7b74cfae58254..43281f5453726 100644 --- a/site/package.json +++ b/site/package.json @@ -27,10 +27,10 @@ "storybook": "STORYBOOK=true storybook dev -p 6006", "storybook:build": "storybook build", "storybook:ci": "storybook build --test", - "test": "jest", - "test:ci": "jest --selectProjects test --silent", - "test:coverage": "jest --selectProjects test --collectCoverage", - "test:watch": "jest --selectProjects test --watch", + "test": "vitest run && jest", + "test:ci": "vitest run && jest --silent", + "test:watch": "vitest", + "test:watch-jest": "jest --watch", "stats": "STATS=true pnpm build && npx http-server ./stats -p 8081 -c-1", "update-emojis": "cp -rf ./node_modules/emoji-datasource-apple/img/apple/64/* ./static/emojis" }, @@ -133,7 +133,7 @@ "@swc/core": "1.3.38", "@swc/jest": "0.2.37", "@tailwindcss/typography": "0.5.16", - "@testing-library/jest-dom": "6.6.3", + "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "14.3.1", "@testing-library/user-event": "14.6.1", "@types/chroma-js": "2.4.0", @@ -167,6 +167,7 @@ "jest-location-mock": "2.0.0", "jest-websocket-mock": "2.5.0", "jest_workaround": "0.1.14", + "jsdom": "27.0.1", "knip": "5.64.1", "msw": "2.4.8", "postcss": "8.5.6", @@ -180,7 +181,8 @@ "ts-proto": "1.181.2", "typescript": "5.6.3", "vite": "7.1.11", - "vite-plugin-checker": "0.11.0" + "vite-plugin-checker": "0.11.0", + "vitest": "4.0.5" }, "browserslist": [ "chrome 110", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index c70eff146bcee..b70a5b755342d 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -309,8 +309,8 @@ importers: specifier: 0.5.16 version: 0.5.16(tailwindcss@3.4.18(yaml@2.7.0)) '@testing-library/jest-dom': - specifier: 6.6.3 - version: 6.6.3 + specifier: 6.9.1 + version: 6.9.1 '@testing-library/react': specifier: 14.3.1 version: 14.3.1(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -410,6 +410,9 @@ importers: jest_workaround: specifier: 0.1.14 version: 0.1.14(@swc/core@1.3.38)(@swc/jest@0.2.37(@swc/core@1.3.38)) + jsdom: + specifier: 27.0.1 + version: 27.0.1 knip: specifier: 5.64.1 version: 5.64.1(@types/node@20.17.16)(typescript@5.6.3) @@ -452,6 +455,9 @@ importers: vite-plugin-checker: specifier: 0.11.0 version: 0.11.0(@biomejs/biome@2.2.4)(eslint@8.52.0)(optionator@0.9.3)(typescript@5.6.3)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + vitest: + specifier: 4.0.5 + version: 4.0.5(@types/debug@4.1.12)(@types/node@20.17.16)(jiti@1.21.7)(jsdom@27.0.1)(msw@2.4.8(typescript@5.6.3))(yaml@2.7.0) packages: @@ -466,6 +472,15 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==, tarball: https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz} engines: {node: '>=10'} + '@asamuzakjp/css-color@4.0.5': + resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==, tarball: https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz} + + '@asamuzakjp/dom-selector@6.7.3': + resolution: {integrity: sha512-kiGFeY+Hxf5KbPpjRLf+ffWbkos1aGo8MBfd91oxS3O57RgU3XhZrt/6UzoVF9VMpWbC3v87SRc9jxGrc9qHtQ==, tarball: https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.3.tgz} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==, tarball: https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==, tarball: https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz} engines: {node: '>=6.9.0'} @@ -727,6 +742,38 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==, tarball: https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz} engines: {node: '>=12'} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==, tarball: https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==, tarball: https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==, tarball: https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==, tarball: https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.15': + resolution: {integrity: sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==, tarball: https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz} + engines: {node: '>=18'} + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==, tarball: https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz} + engines: {node: '>=18'} + '@emnapi/core@1.5.0': resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==, tarball: https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz} @@ -2557,6 +2604,9 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==, tarball: https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==, tarball: https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz} + '@storybook/addon-docs@9.1.2': resolution: {integrity: sha512-U3eHJ8lQFfEZ/OcgdKkUBbW2Y2tpAsHfy8lQOBgs5Pgj9biHEJcUmq+drOS/sJhle673eoBcUFmspXulI4KP1w==, tarball: https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-9.1.2.tgz} peerDependencies: @@ -2728,8 +2778,8 @@ packages: resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==, tarball: https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz} engines: {node: '>=14'} - '@testing-library/jest-dom@6.6.3': - resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==, tarball: https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz} + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==, tarball: https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} '@testing-library/react@14.3.1': @@ -3057,6 +3107,9 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==, tarball: https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz} + '@vitest/expect@4.0.5': + resolution: {integrity: sha512-DJctLVlKoddvP/G389oGmKWNG6GD9frm2FPXARziU80Rjo7SIYxQzb2YFzmQ4fVD3Q5utUYY8nUmWrqsuIlIXQ==, tarball: https://registry.npmjs.org/@vitest/expect/-/expect-4.0.5.tgz} + '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==, tarball: https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz} peerDependencies: @@ -3068,15 +3121,41 @@ packages: vite: optional: true + '@vitest/mocker@4.0.5': + resolution: {integrity: sha512-iYHIy72LfbK+mL5W8zXROp6oOcJKXWeKcNjcPPsqoa18qIEDrhB6/Z08o0wRajTd6SSSDNw8NCSIHVNOMpz0mw==, tarball: https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.5.tgz} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==, tarball: https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz} + '@vitest/pretty-format@4.0.5': + resolution: {integrity: sha512-t1T/sSdsYyNc5AZl0EMeD0jW9cpJe2cODP0R++ZQe1kTkpgrwEfxGFR/yCG4w8ZybizbXRTHU7lE8sTDD/QsGw==, tarball: https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.5.tgz} + + '@vitest/runner@4.0.5': + resolution: {integrity: sha512-CQVVe+YEeKSiFBD5gBAmRDQglm4PnMBYzeTmt06t5iWtsUN9StQeeKhYCea/oaqBYilf8sARG6fSctUcEL/UmQ==, tarball: https://registry.npmjs.org/@vitest/runner/-/runner-4.0.5.tgz} + + '@vitest/snapshot@4.0.5': + resolution: {integrity: sha512-jfmSAeR6xYNEvcD+/RxFGA1bzpqHtkVhgxo2cxXia+Q3xX7m6GpZij07rz+WyQcA/xEGn4eIS1OItkMyWsGBmQ==, tarball: https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.5.tgz} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==, tarball: https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz} + '@vitest/spy@4.0.5': + resolution: {integrity: sha512-TUmVQpAQign7r8+EnZsgTF3vY9BdGofTUge1rGNbnHn2IN3FChiQoT9lrPz7A7AVUZJU2LAZXl4v66HhsNMhoA==, tarball: https://registry.npmjs.org/@vitest/spy/-/spy-4.0.5.tgz} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz} + '@vitest/utils@4.0.5': + resolution: {integrity: sha512-V5RndUgCB5/AfNvK9zxGCrRs99IrPYtMTIdUzJMMFs9nrmE5JXExIEfjVtUteyTRiLfCm+dCRMHf/Uu7Mm8/dg==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-4.0.5.tgz} + '@xterm/addon-canvas@0.7.0': resolution: {integrity: sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw==, tarball: https://registry.npmjs.org/@xterm/addon-canvas/-/addon-canvas-0.7.0.tgz} peerDependencies: @@ -3144,6 +3223,10 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==, tarball: https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz} engines: {node: '>= 6.0.0'} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==, tarball: https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, tarball: https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz} @@ -3304,6 +3387,9 @@ packages: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==, tarball: https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz} engines: {node: '>=12.0.0'} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==, tarball: https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==, tarball: https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz} engines: {node: '>=8'} @@ -3390,9 +3476,9 @@ packages: resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==, tarball: https://registry.npmjs.org/chai/-/chai-5.2.1.tgz} engines: {node: '>=18'} - chalk@3.0.0: - resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==, tarball: https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz} - engines: {node: '>=8'} + chai@6.2.0: + resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==, tarball: https://registry.npmjs.org/chai/-/chai-6.2.0.tgz} + engines: {node: '>=18'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, tarball: https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz} @@ -3599,6 +3685,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, tarball: https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz} engines: {node: '>= 8'} + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==, tarball: https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==, tarball: https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz} @@ -3620,6 +3710,10 @@ packages: resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==, tarball: https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz} engines: {node: '>=8'} + cssstyle@5.3.1: + resolution: {integrity: sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==, tarball: https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz} + engines: {node: '>=20'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==, tarball: https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz} @@ -3671,6 +3765,10 @@ packages: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==, tarball: https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz} engines: {node: '>=12'} + data-urls@6.0.0: + resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==, tarball: https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz} + engines: {node: '>=20'} + date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==, tarball: https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz} engines: {node: '>=0.11'} @@ -3710,6 +3808,9 @@ packages: decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==, tarball: https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==, tarball: https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz} + decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==, tarball: https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz} @@ -3869,6 +3970,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==, tarball: https://registry.npmjs.org/entities/-/entities-4.5.0.tgz} engines: {node: '>=0.12'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==, tarball: https://registry.npmjs.org/entities/-/entities-6.0.1.tgz} + engines: {node: '>=0.12'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==, tarball: https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz} @@ -3883,6 +3988,9 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==, tarball: https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, tarball: https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, tarball: https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz} engines: {node: '>= 0.4'} @@ -3993,6 +4101,10 @@ packages: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==, tarball: https://registry.npmjs.org/exit/-/exit-0.1.2.tgz} engines: {node: '>= 0.8.0'} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==, tarball: https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz} + engines: {node: '>=12.0.0'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==, tarball: https://registry.npmjs.org/expect/-/expect-29.7.0.tgz} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4278,6 +4390,10 @@ packages: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==, tarball: https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz} engines: {node: '>=12'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==, tarball: https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz} + engines: {node: '>=18'} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, tarball: https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz} @@ -4292,10 +4408,18 @@ packages: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==, tarball: https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz} engines: {node: '>= 6'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==, tarball: https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz} + engines: {node: '>= 14'} + https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==, tarball: https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz} engines: {node: '>= 6'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, tarball: https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, tarball: https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz} engines: {node: '>=10.17.0'} @@ -4751,6 +4875,15 @@ packages: canvas: optional: true + jsdom@27.0.1: + resolution: {integrity: sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==, tarball: https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz} + engines: {node: '>=20'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, tarball: https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz} engines: {node: '>=6'} @@ -4868,6 +5001,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz} + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz} @@ -4887,6 +5024,9 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==, tarball: https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, tarball: https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, tarball: https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz} engines: {node: '>=10'} @@ -4952,6 +5092,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==, tarball: https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==, tarball: https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==, tarball: https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz} engines: {node: '>= 0.6'} @@ -5278,6 +5421,9 @@ packages: parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==, tarball: https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==, tarball: https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, tarball: https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz} engines: {node: '>= 0.8'} @@ -5319,6 +5465,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, tarball: https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, tarball: https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz} + pathval@2.0.0: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==, tarball: https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz} engines: {node: '>= 14.16'} @@ -5727,6 +5876,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, tarball: https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, tarball: https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz} + engines: {node: '>=0.10.0'} + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==, tarball: https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz} @@ -5785,6 +5938,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==, tarball: https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, tarball: https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz} @@ -5864,6 +6020,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, tarball: https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, tarball: https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, tarball: https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz} @@ -5918,6 +6077,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==, tarball: https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz} engines: {node: '>=10'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, tarball: https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz} + state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==, tarball: https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz} @@ -5929,6 +6091,9 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==, tarball: https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz} engines: {node: '>= 0.8'} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==, tarball: https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz} + stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==, tarball: https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz} engines: {node: '>= 0.4'} @@ -6084,9 +6249,15 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==, tarball: https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, tarball: https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz} + tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==, tarball: https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, tarball: https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, tarball: https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz} engines: {node: '>=12.0.0'} @@ -6095,10 +6266,21 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==, tarball: https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz} engines: {node: '>=14.0.0'} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==, tarball: https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz} + engines: {node: '>=14.0.0'} + tinyspy@4.0.3: resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==, tarball: https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz} engines: {node: '>=14.0.0'} + tldts-core@7.0.17: + resolution: {integrity: sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==, tarball: https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz} + + tldts@7.0.17: + resolution: {integrity: sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==, tarball: https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz} + hasBin: true + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==, tarball: https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz} @@ -6117,10 +6299,18 @@ packages: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==, tarball: https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz} engines: {node: '>=6'} + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==, tarball: https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz} + engines: {node: '>=16'} + tr46@3.0.0: resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==, tarball: https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz} engines: {node: '>=12'} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==, tarball: https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz} + engines: {node: '>=20'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==, tarball: https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz} @@ -6444,6 +6634,40 @@ packages: yaml: optional: true + vitest@4.0.5: + resolution: {integrity: sha512-4H+J28MI5oeYgGg3h5BFSkQ1g/2GKK1IR8oorH3a6EQQbb7CwjbnyBjH4PGxw9/6vpwAPNzaeUMp4Js4WJmdXQ==, tarball: https://registry.npmjs.org/vitest/-/vitest-4.0.5.tgz} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.5 + '@vitest/browser-preview': 4.0.5 + '@vitest/browser-webdriverio': 4.0.5 + '@vitest/ui': 4.0.5 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==, tarball: https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz} @@ -6451,6 +6675,10 @@ packages: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==, tarball: https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz} engines: {node: '>=14'} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==, tarball: https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz} + engines: {node: '>=18'} + walk-up-path@4.0.0: resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==, tarball: https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz} engines: {node: 20 || >=22} @@ -6465,6 +6693,10 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==, tarball: https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz} engines: {node: '>=12'} + webidl-conversions@8.0.0: + resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==, tarball: https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz} + engines: {node: '>=20'} + webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==, tarball: https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz} engines: {node: '>=10.13.0'} @@ -6479,14 +6711,26 @@ packages: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==, tarball: https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz} engines: {node: '>=12'} + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==, tarball: https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz} + engines: {node: '>=18'} + whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==, tarball: https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz} engines: {node: '>=12'} + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==, tarball: https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz} + engines: {node: '>=18'} + whatwg-url@11.0.0: resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==, tarball: https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz} engines: {node: '>=12'} + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==, tarball: https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz} + engines: {node: '>=20'} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==, tarball: https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz} @@ -6502,6 +6746,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, tarball: https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz} + engines: {node: '>=8'} + hasBin: true + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==, tarball: https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz} engines: {node: '>=8'} @@ -6545,10 +6794,26 @@ packages: utf-8-validate: optional: true + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==, tarball: https://registry.npmjs.org/ws/-/ws-8.18.3.tgz} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==, tarball: https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz} engines: {node: '>=12'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==, tarball: https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz} + engines: {node: '>=18'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==, tarball: https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz} @@ -6614,6 +6879,24 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@asamuzakjp/css-color@4.0.5': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 11.2.2 + + '@asamuzakjp/dom-selector@6.7.3': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.2 + + '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -6899,6 +7182,28 @@ snapshots: '@jridgewell/trace-mapping': 0.3.9 optional: true + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.15': {} + + '@csstools/css-tokenizer@3.0.4': {} + '@emnapi/core@1.5.0': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -8640,6 +8945,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.0 + '@standard-schema/spec@1.0.0': {} + '@storybook/addon-docs@9.1.2(@types/react@19.1.17)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: '@mdx-js/react': 3.0.1(@types/react@19.1.17)(react@19.1.1) @@ -8817,14 +9124,13 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.6.3': + '@testing-library/jest-dom@6.9.1': dependencies: '@adobe/css-tools': 4.4.1 aria-query: 5.3.2 - chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 - lodash: 4.17.21 + picocolors: 1.1.1 redent: 3.0.0 '@testing-library/react@14.3.1(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': @@ -9178,6 +9484,15 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 + '@vitest/expect@4.0.5': + dependencies: + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.2 + '@vitest/spy': 4.0.5 + '@vitest/utils': 4.0.5 + chai: 6.2.0 + tinyrainbow: 3.0.3 + '@vitest/mocker@3.2.4(msw@2.4.8(typescript@5.6.3))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.2.4 @@ -9187,20 +9502,51 @@ snapshots: msw: 2.4.8(typescript@5.6.3) vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + '@vitest/mocker@4.0.5(msw@2.4.8(typescript@5.6.3))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + dependencies: + '@vitest/spy': 4.0.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.4.8(typescript@5.6.3) + vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.0.5': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.5': + dependencies: + '@vitest/utils': 4.0.5 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.5': + dependencies: + '@vitest/pretty-format': 4.0.5 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.3 + '@vitest/spy@4.0.5': {} + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 loupe: 3.2.0 tinyrainbow: 2.0.0 + '@vitest/utils@4.0.5': + dependencies: + '@vitest/pretty-format': 4.0.5 + tinyrainbow: 3.0.3 + '@xterm/addon-canvas@0.7.0(@xterm/xterm@5.5.0)': dependencies: '@xterm/xterm': 5.5.0 @@ -9257,6 +9603,8 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -9443,6 +9791,10 @@ snapshots: dependencies: open: 8.4.2 + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + binary-extensions@2.3.0: {} bl@4.1.0: @@ -9548,10 +9900,7 @@ snapshots: loupe: 3.2.0 pathval: 2.0.0 - chalk@3.0.0: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + chai@6.2.0: {} chalk@4.1.2: dependencies: @@ -9726,6 +10075,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + css.escape@1.5.1: {} cssesc@3.0.0: {} @@ -9740,6 +10094,12 @@ snapshots: dependencies: cssom: 0.3.8 + cssstyle@5.3.1: + dependencies: + '@asamuzakjp/css-color': 4.0.5 + '@csstools/css-syntax-patches-for-csstree': 1.0.15 + css-tree: 3.1.0 + csstype@3.1.3: {} d3-array@3.2.4: @@ -9786,6 +10146,11 @@ snapshots: whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 + data-urls@6.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + date-fns@2.30.0: dependencies: '@babel/runtime': 7.26.10 @@ -9808,6 +10173,8 @@ snapshots: decimal.js@10.4.3: {} + decimal.js@10.6.0: {} + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -9956,6 +10323,8 @@ snapshots: entities@4.5.0: {} + entities@6.0.1: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -9976,6 +10345,8 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.0.0 + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -10171,6 +10542,8 @@ snapshots: exit@0.1.2: {} + expect-type@1.2.2: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -10519,6 +10892,10 @@ snapshots: dependencies: whatwg-encoding: 2.0.0 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-escaper@2.0.2: {} html-url-attributes@3.0.1: {} @@ -10539,6 +10916,13 @@ snapshots: transitivePeerDependencies: - supports-color + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -10546,6 +10930,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} humanize-duration@3.32.2: {} @@ -11210,6 +11601,33 @@ snapshots: - supports-color - utf-8-validate + jsdom@27.0.1: + dependencies: + '@asamuzakjp/dom-selector': 6.7.3 + cssstyle: 5.3.1 + data-urls: 6.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json-buffer@3.0.1: @@ -11327,6 +11745,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.2.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -11343,6 +11763,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + make-dir@4.0.0: dependencies: semver: 7.7.2 @@ -11513,6 +11937,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdn-data@2.12.2: {} + media-typer@0.3.0: {} memoize-one@5.2.1: {} @@ -11970,6 +12396,10 @@ snapshots: dependencies: entities: 4.5.0 + parse5@8.0.0: + dependencies: + entities: 6.0.1 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -11995,6 +12425,8 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + pathval@2.0.0: {} picocolors@1.1.1: {} @@ -12454,6 +12886,8 @@ snapshots: require-directory@2.1.1: {} + require-from-string@2.0.2: {} + requires-port@1.0.0: {} resize-observer-polyfill@1.5.1: {} @@ -12523,6 +12957,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.52.5 fsevents: 2.3.3 + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -12629,6 +13065,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -12670,12 +13108,16 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stackback@0.0.2: {} + state-local@1.0.7: {} statuses@2.0.1: {} statuses@2.0.2: {} + std-env@3.10.0: {} + stop-iteration-iterator@1.0.0: dependencies: internal-slot: 1.0.6 @@ -12694,7 +13136,7 @@ snapshots: storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)): dependencies: '@storybook/global': 5.0.0 - '@testing-library/jest-dom': 6.6.3 + '@testing-library/jest-dom': 6.9.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) '@vitest/expect': 3.2.4 '@vitest/mocker': 3.2.4(msw@2.4.8(typescript@5.6.3))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) @@ -12866,8 +13308,12 @@ snapshots: tiny-warning@1.0.3: {} + tinybench@2.9.0: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -12875,8 +13321,16 @@ snapshots: tinyrainbow@2.0.0: {} + tinyrainbow@3.0.3: {} + tinyspy@4.0.3: {} + tldts-core@7.0.17: {} + + tldts@7.0.17: + dependencies: + tldts-core: 7.0.17 + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -12894,10 +13348,18 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.17 + tr46@3.0.0: dependencies: punycode: 2.3.1 + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -13173,12 +13635,56 @@ snapshots: jiti: 1.21.7 yaml: 2.7.0 + vitest@4.0.5(@types/debug@4.1.12)(@types/node@20.17.16)(jiti@1.21.7)(jsdom@27.0.1)(msw@2.4.8(typescript@5.6.3))(yaml@2.7.0): + dependencies: + '@vitest/expect': 4.0.5 + '@vitest/mocker': 4.0.5(msw@2.4.8(typescript@5.6.3))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + '@vitest/pretty-format': 4.0.5 + '@vitest/runner': 4.0.5 + '@vitest/snapshot': 4.0.5 + '@vitest/spy': 4.0.5 + '@vitest/utils': 4.0.5 + debug: 4.4.3 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 20.17.16 + jsdom: 27.0.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vscode-uri@3.1.0: {} w3c-xmlserializer@4.0.0: dependencies: xml-name-validator: 4.0.0 + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + walk-up-path@4.0.0: {} walker@1.0.8: @@ -13191,6 +13697,8 @@ snapshots: webidl-conversions@7.0.0: {} + webidl-conversions@8.0.0: {} + webpack-sources@3.2.3: {} webpack-virtual-modules@0.5.0: {} @@ -13201,13 +13709,24 @@ snapshots: dependencies: iconv-lite: 0.6.3 + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + whatwg-mimetype@3.0.0: {} + whatwg-mimetype@4.0.0: {} + whatwg-url@11.0.0: dependencies: tr46: 3.0.0 webidl-conversions: 7.0.0 + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.0 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -13236,6 +13755,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -13265,8 +13789,12 @@ snapshots: ws@8.18.0: {} + ws@8.18.3: {} + xml-name-validator@4.0.0: {} + xml-name-validator@5.0.0: {} + xmlchars@2.2.0: {} xtend@4.0.2: {} diff --git a/site/src/api/api.test.ts b/site/src/api/api.jest.ts similarity index 100% rename from site/src/api/api.test.ts rename to site/src/api/api.jest.ts diff --git a/site/src/api/api.ts b/site/src/api/api.ts index baf56e5a27584..8c665e3c6023d 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -2759,7 +2759,7 @@ function getConfiguredAxiosInstance(): AxiosInstance { } } else { // Do not write error logs if we are in a FE unit test. - if (process.env.JEST_WORKER_ID === undefined) { + if (!process.env.JEST_WORKER_ID && !process.env.VITEST) { console.error("CSRF token not found"); } } diff --git a/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.test.tsx b/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.test.tsx index 72ce09290dfd1..8ec97302ed6fd 100644 --- a/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.test.tsx +++ b/site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.test.tsx @@ -5,7 +5,7 @@ import { ConfirmDialog } from "./ConfirmDialog"; describe("ConfirmDialog", () => { it("onClose is called when cancelled", () => { // Given - const onCloseMock = jest.fn(); + const onCloseMock = vi.fn(); const props = { cancelText: "CANCEL", hideCancel: false, @@ -24,8 +24,8 @@ describe("ConfirmDialog", () => { it("onConfirm is called when confirmed", () => { // Given - const onCloseMock = jest.fn(); - const onConfirmMock = jest.fn(); + const onCloseMock = vi.fn(); + const onConfirmMock = vi.fn(); const props = { cancelText: "CANCEL", confirmText: "CONFIRM", diff --git a/site/src/components/Dialogs/DeleteDialog/DeleteDialog.test.tsx b/site/src/components/Dialogs/DeleteDialog/DeleteDialog.test.tsx index 94fe49bcd3639..1ef31597475f4 100644 --- a/site/src/components/Dialogs/DeleteDialog/DeleteDialog.test.tsx +++ b/site/src/components/Dialogs/DeleteDialog/DeleteDialog.test.tsx @@ -23,8 +23,8 @@ describe("DeleteDialog", () => { renderComponent( , @@ -38,8 +38,8 @@ describe("DeleteDialog", () => { renderComponent( , @@ -56,8 +56,8 @@ describe("DeleteDialog", () => { renderComponent( , diff --git a/site/src/components/FileUpload/FileUpload.test.tsx b/site/src/components/FileUpload/FileUpload.test.tsx index e3ebce085ce50..f72425152e194 100644 --- a/site/src/components/FileUpload/FileUpload.test.tsx +++ b/site/src/components/FileUpload/FileUpload.test.tsx @@ -3,7 +3,7 @@ import { fireEvent, screen } from "@testing-library/react"; import { FileUpload } from "./FileUpload"; test("accepts files with the correct extension", async () => { - const onUpload = jest.fn(); + const onUpload = vi.fn(); renderComponent( { fireEvent.drop(dropZone, { dataTransfer: { files: [tarFile] }, }); - expect(onUpload).toBeCalledWith(tarFile); + expect(onUpload).toHaveBeenCalledWith(tarFile); onUpload.mockClear(); const zipFile = new File([""], "file.zip"); fireEvent.drop(dropZone, { dataTransfer: { files: [zipFile] }, }); - expect(onUpload).toBeCalledWith(zipFile); + expect(onUpload).toHaveBeenCalledWith(zipFile); onUpload.mockClear(); const unsupportedFile = new File([""], "file.mp4"); diff --git a/site/src/components/GlobalSnackbar/utils.test.ts b/site/src/components/GlobalSnackbar/utils.test.ts index cc2c33b3d3025..3ce76c33d61c0 100644 --- a/site/src/components/GlobalSnackbar/utils.test.ts +++ b/site/src/components/GlobalSnackbar/utils.test.ts @@ -1,3 +1,4 @@ +import type { Mock } from "vitest"; import { displayError, displaySuccess, @@ -48,7 +49,7 @@ describe("Snackbar", () => { describe("displaySuccess", () => { const originalWindowDispatchEvent = window.dispatchEvent; - type TDispatchEventMock = jest.MockedFunction< + type TDispatchEventMock = Mock< (msg: CustomEvent) => boolean >; let dispatchEventMock: TDispatchEventMock; @@ -67,7 +68,7 @@ describe("Snackbar", () => { }; beforeEach(() => { - dispatchEventMock = jest.fn(); + dispatchEventMock = vi.fn(); window.dispatchEvent = dispatchEventMock as unknown as typeof window.dispatchEvent; }); @@ -114,16 +115,18 @@ describe("Snackbar", () => { }); describe("displayError", () => { - it("shows the title and the message", (done) => { + it("shows the title and the message", () => { const message = "Some error happened"; - window.addEventListener(SnackbarEventType, (event) => { - const notificationEvent = event as CustomEvent; - expect(notificationEvent.detail.msg).toEqual(message); - done(); - }); + return new Promise((resolve) => { + window.addEventListener(SnackbarEventType, (event) => { + const notificationEvent = event as CustomEvent; + expect(notificationEvent.detail.msg).toEqual(message); + resolve(); + }); - displayError(message); + displayError(message); + }); }); }); }); diff --git a/site/src/components/PaginationWidget/PaginationWidgetBase.test.tsx b/site/src/components/PaginationWidget/PaginationWidgetBase.jest.tsx similarity index 100% rename from site/src/components/PaginationWidget/PaginationWidgetBase.test.tsx rename to site/src/components/PaginationWidget/PaginationWidgetBase.jest.tsx diff --git a/site/src/contexts/ProxyContext.test.tsx b/site/src/contexts/ProxyContext.jest.tsx similarity index 100% rename from site/src/contexts/ProxyContext.test.tsx rename to site/src/contexts/ProxyContext.jest.tsx diff --git a/site/src/contexts/auth/AuthProvider.test.tsx b/site/src/contexts/auth/AuthProvider.test.tsx deleted file mode 100644 index bf3ea34a7992a..0000000000000 --- a/site/src/contexts/auth/AuthProvider.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { createTestQueryClient } from "testHelpers/renderHelpers"; -import { renderHook } from "@testing-library/react"; -import type { FC, PropsWithChildren } from "react"; -import { QueryClientProvider } from "react-query"; -import { AuthProvider, useAuthContext } from "./AuthProvider"; - -const Wrapper: FC = ({ children }) => { - return ( - - {children} - - ); -}; - -describe("useAuth", () => { - it("throws an error if it is used outside of ", () => { - jest.spyOn(console, "error").mockImplementation(() => {}); - - expect(() => { - renderHook(() => useAuthContext()); - }).toThrow("useAuth should be used inside of "); - - jest.restoreAllMocks(); - }); - - it("returns AuthContextValue when used inside of ", () => { - expect(() => { - renderHook(() => useAuthContext(), { - wrapper: Wrapper, - }); - }).not.toThrow(); - }); -}); diff --git a/site/src/contexts/auth/RequireAuth.test.tsx b/site/src/contexts/auth/RequireAuth.jest.tsx similarity index 100% rename from site/src/contexts/auth/RequireAuth.test.tsx rename to site/src/contexts/auth/RequireAuth.jest.tsx diff --git a/site/src/hooks/debounce.test.ts b/site/src/hooks/debounce.jest.ts similarity index 100% rename from site/src/hooks/debounce.test.ts rename to site/src/hooks/debounce.jest.ts diff --git a/site/src/hooks/events.test.ts b/site/src/hooks/events.test.ts index de2a18c0f6bfd..b7cc28457245f 100644 --- a/site/src/hooks/events.test.ts +++ b/site/src/hooks/events.test.ts @@ -4,14 +4,14 @@ import { useCustomEvent } from "./events"; describe(useCustomEvent.name, () => { it("Should receive custom events dispatched by the dispatchCustomEvent function", async () => { - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); const eventType = "testEvent"; const detail = { title: "We have a new event!" }; renderHook(() => useCustomEvent(eventType, mockCallback)); dispatchCustomEvent(eventType, detail); - await waitFor(() => expect(mockCallback).toBeCalledTimes(1)); + await waitFor(() => expect(mockCallback).toHaveBeenCalledTimes(1)); expect(mockCallback.mock.calls[0]?.[0]?.detail).toBe(detail); }); }); diff --git a/site/src/hooks/hookPolyfills.test.ts b/site/src/hooks/hookPolyfills.test.ts index 3e603b17204ae..23342bb59cc5d 100644 --- a/site/src/hooks/hookPolyfills.test.ts +++ b/site/src/hooks/hookPolyfills.test.ts @@ -15,7 +15,7 @@ describe(useEffectEvent.name, () => { } it("Should maintain a stable reference across all renders", () => { - const callback = jest.fn(); + const callback = vi.fn(); const { result, rerender } = renderEffectEvent(callback); const firstResult = result.current; @@ -28,8 +28,8 @@ describe(useEffectEvent.name, () => { }); it("Should always call the most recent callback passed in", () => { - const mockCallback1 = jest.fn(); - const mockCallback2 = jest.fn(); + const mockCallback1 = vi.fn(); + const mockCallback2 = vi.fn(); const { result, rerender } = renderEffectEvent(mockCallback1); rerender({ callback: mockCallback2 }); diff --git a/site/src/hooks/useClickable.test.tsx b/site/src/hooks/useClickable.test.tsx index 4a564bc8ceb9a..ea18fb33e44f3 100644 --- a/site/src/hooks/useClickable.test.tsx +++ b/site/src/hooks/useClickable.test.tsx @@ -35,14 +35,14 @@ const NonNativeButton: FC> = ({ describe(useClickable.name, () => { it("Always defaults to role 'button'", () => { - render(); + render(); expect(() => screen.getByRole("button")).not.toThrow(); }); it("Overrides the native role of any element that receives the hook result (be very careful with this behavior)", () => { const anchorText = "I'm a button that's secretly a link!"; render( - + {anchorText} , ); @@ -55,7 +55,7 @@ describe(useClickable.name, () => { }); it("Always returns out the same role override received via arguments", () => { - const placeholderCallback = jest.fn(); + const placeholderCallback = vi.fn(); const roles = [ "button", "switch", @@ -73,7 +73,7 @@ describe(useClickable.name, () => { it("Allows an element to receive keyboard focus", async () => { const user = userEvent.setup(); - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); render(, { wrapper: ({ children }) => ( @@ -90,7 +90,7 @@ describe(useClickable.name, () => { }); it("Allows an element to respond to clicks and Space/Enter, following all rules for native Button element interactions", async () => { - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); const user = userEvent.setup(); render(); @@ -107,7 +107,7 @@ describe(useClickable.name, () => { }); it("Will keep firing events if the Enter key is held down", async () => { - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); const user = userEvent.setup(); render(); @@ -119,7 +119,7 @@ describe(useClickable.name, () => { }); it("Will NOT keep firing events if the Space key is held down", async () => { - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); const user = userEvent.setup(); render(); @@ -133,7 +133,7 @@ describe(useClickable.name, () => { }); test("If focus is lost while Space is held down, then releasing the key will do nothing", async () => { - const mockCallback = jest.fn(); + const mockCallback = vi.fn(); const user = userEvent.setup(); render(, { diff --git a/site/src/hooks/useClipboard.test.tsx b/site/src/hooks/useClipboard.jest.tsx similarity index 100% rename from site/src/hooks/useClipboard.test.tsx rename to site/src/hooks/useClipboard.jest.tsx diff --git a/site/src/hooks/useEmbeddedMetadata.test.ts b/site/src/hooks/useEmbeddedMetadata.jest.ts similarity index 100% rename from site/src/hooks/useEmbeddedMetadata.test.ts rename to site/src/hooks/useEmbeddedMetadata.jest.ts diff --git a/site/src/hooks/usePaginatedQuery.test.ts b/site/src/hooks/usePaginatedQuery.jest.ts similarity index 96% rename from site/src/hooks/usePaginatedQuery.test.ts rename to site/src/hooks/usePaginatedQuery.jest.ts index bb62f2b4b2628..3811cd134c7a6 100644 --- a/site/src/hooks/usePaginatedQuery.test.ts +++ b/site/src/hooks/usePaginatedQuery.jest.ts @@ -1,3 +1,9 @@ +// TODO: This test is timing out after upgrade a few Jest dependencies +// and I was not able to figure out why. When running it specifically, I +// can see many act warnings that may can help us to find the issue. +// (Note: This comment was originally written by Bruno, and was relocated by +// me. If you go poking at `git blame`, disabling these tests was not my idea. + import { renderHookWithAuth } from "testHelpers/hooks"; import { waitFor } from "@testing-library/react"; import { @@ -303,7 +309,7 @@ describe.skip(usePaginatedQuery.name, () => { }); }); -describe(`${usePaginatedQuery.name} - Returned properties`, () => { +describe.skip(`${usePaginatedQuery.name} - Returned properties`, () => { describe("Page change methods", () => { const mockQueryKey = jest.fn(() => ["mock"]); diff --git a/site/src/hooks/useSearchParamsKey.test.ts b/site/src/hooks/useSearchParamsKey.jest.ts similarity index 100% rename from site/src/hooks/useSearchParamsKey.test.ts rename to site/src/hooks/useSearchParamsKey.jest.ts diff --git a/site/src/modules/dashboard/DashboardLayout.test.tsx b/site/src/modules/dashboard/DashboardLayout.jest.tsx similarity index 100% rename from site/src/modules/dashboard/DashboardLayout.test.tsx rename to site/src/modules/dashboard/DashboardLayout.jest.tsx diff --git a/site/src/modules/dashboard/Navbar/Navbar.test.tsx b/site/src/modules/dashboard/Navbar/Navbar.test.tsx deleted file mode 100644 index 2390d315ce9b5..0000000000000 --- a/site/src/modules/dashboard/Navbar/Navbar.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { App } from "App"; -import { - MockEntitlementsWithAuditLog, - MockMemberPermissions, -} from "testHelpers/entities"; -import { server } from "testHelpers/server"; -import { render, screen, waitFor } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { HttpResponse, http } from "msw"; - -/** - * The LicenseBanner, mounted above the AppRouter, fetches entitlements. Thus, to test their - * effects, we must test at the App level and `waitFor` the fetch to be done. - */ -describe("Navbar", () => { - it("shows Audit Log link when permitted and entitled", async () => { - // set entitlements to allow audit log - server.use( - http.get("/api/v2/entitlements", () => { - return HttpResponse.json(MockEntitlementsWithAuditLog); - }), - ); - render(); - const deploymentMenu = await screen.findByText("Admin settings"); - await userEvent.click(deploymentMenu); - await screen.findByText("Audit Logs"); - }); - - it("does not show Audit Log link when not entitled", async () => { - // by default, user is an Admin with permission to see the audit log, - // but is unlicensed so not entitled to see the audit log - render(); - const deploymentMenu = await screen.findByText("Admin settings"); - await userEvent.click(deploymentMenu); - await waitFor( - () => { - expect(screen.queryByText("Audit Logs")).not.toBeInTheDocument(); - }, - { timeout: 2000 }, - ); - }); - - it("does not show Audit Log link when not permitted via role", async () => { - // set permissions to Member (can't audit) - server.use( - http.post("/api/v2/authcheck", async () => { - return HttpResponse.json(MockMemberPermissions); - }), - ); - // set entitlements to allow audit log - server.use( - http.get("/api/v2/entitlements", () => { - return HttpResponse.json(MockEntitlementsWithAuditLog); - }), - ); - render(); - await waitFor( - () => { - expect(screen.queryByText("Deployment")).not.toBeInTheDocument(); - }, - { timeout: 2000 }, - ); - }); -}); diff --git a/site/src/modules/dashboard/Navbar/NavbarView.test.tsx b/site/src/modules/dashboard/Navbar/NavbarView.test.tsx deleted file mode 100644 index f313b6aa2b33e..0000000000000 --- a/site/src/modules/dashboard/Navbar/NavbarView.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { MockPrimaryWorkspaceProxy, MockUserOwner } from "testHelpers/entities"; -import { renderWithAuth } from "testHelpers/renderHelpers"; -import { screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import type { ProxyContextValue } from "contexts/ProxyContext"; -import { NavbarView } from "./NavbarView"; - -const proxyContextValue: ProxyContextValue = { - latenciesLoaded: true, - proxy: { - preferredPathAppURL: "", - preferredWildcardHostname: "", - proxy: MockPrimaryWorkspaceProxy, - }, - isLoading: false, - isFetched: true, - setProxy: jest.fn(), - clearProxy: jest.fn(), - refetchProxyLatencies: jest.fn(), - proxyLatencies: {}, -}; - -describe("NavbarView", () => { - const noop = jest.fn(); - - it("workspaces nav link has the correct href", async () => { - renderWithAuth( - , - ); - const workspacesLink = - await screen.findByText(/workspaces/i); - expect(workspacesLink.href).toContain("/workspaces"); - }); - - it("templates nav link has the correct href", async () => { - renderWithAuth( - , - ); - const templatesLink = - await screen.findByText(/templates/i); - expect(templatesLink.href).toContain("/templates"); - }); - - it("audit nav link has the correct href", async () => { - renderWithAuth( - , - ); - const deploymentMenu = await screen.findByText("Admin settings"); - await userEvent.click(deploymentMenu); - const auditLink = await screen.findByText(/audit logs/i); - expect(auditLink.href).toContain("/audit"); - }); - - it("deployment nav link has the correct href", async () => { - renderWithAuth( - , - ); - const deploymentMenu = await screen.findByText("Admin settings"); - await userEvent.click(deploymentMenu); - const deploymentSettingsLink = - await screen.findByText(/deployment/i); - expect(deploymentSettingsLink.href).toContain("/deployment"); - }); -}); diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx index 1d25d894eacb6..a134d706d1f3d 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx @@ -10,7 +10,7 @@ describe("UserDropdownContent", () => { , @@ -26,7 +26,7 @@ describe("UserDropdownContent", () => { }); it("calls the onSignOut function", async () => { - const onSignOut = jest.fn(); + const onSignOut = vi.fn(); render( { - it("renders component", async () => { - renderComponent( - - - , - ); - - expect( - screen.getByText(MockListeningPortsResponse.ports[0].port), - ).toBeInTheDocument(); - - expect( - screen.getByText(MockListeningPortsResponse.ports[0].process_name), - ).toBeInTheDocument(); - }); -}); diff --git a/site/src/modules/resources/useAgentContainers.test.tsx b/site/src/modules/resources/useAgentContainers.jest.tsx similarity index 100% rename from site/src/modules/resources/useAgentContainers.test.tsx rename to site/src/modules/resources/useAgentContainers.jest.tsx diff --git a/site/src/modules/resources/useAgentLogs.test.ts b/site/src/modules/resources/useAgentLogs.jest.ts similarity index 100% rename from site/src/modules/resources/useAgentLogs.test.ts rename to site/src/modules/resources/useAgentLogs.jest.ts diff --git a/site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.test.tsx b/site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.jest.tsx similarity index 100% rename from site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.test.tsx rename to site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.jest.tsx diff --git a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.test.tsx b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.jest.tsx similarity index 100% rename from site/src/modules/workspaces/DynamicParameter/DynamicParameter.test.tsx rename to site/src/modules/workspaces/DynamicParameter/DynamicParameter.jest.tsx diff --git a/site/src/modules/workspaces/WorkspaceMoreActions/useWorkspaceDuplication.test.tsx b/site/src/modules/workspaces/WorkspaceMoreActions/useWorkspaceDuplication.jest.tsx similarity index 100% rename from site/src/modules/workspaces/WorkspaceMoreActions/useWorkspaceDuplication.test.tsx rename to site/src/modules/workspaces/WorkspaceMoreActions/useWorkspaceDuplication.jest.tsx diff --git a/site/src/pages/AuditPage/AuditPage.test.tsx b/site/src/pages/AuditPage/AuditPage.jest.tsx similarity index 100% rename from site/src/pages/AuditPage/AuditPage.test.tsx rename to site/src/pages/AuditPage/AuditPage.jest.tsx diff --git a/site/src/pages/ConnectionLogPage/ConnectionLogPage.test.tsx b/site/src/pages/ConnectionLogPage/ConnectionLogPage.jest.tsx similarity index 100% rename from site/src/pages/ConnectionLogPage/ConnectionLogPage.test.tsx rename to site/src/pages/ConnectionLogPage/ConnectionLogPage.jest.tsx diff --git a/site/src/pages/CreateTemplateGalleryPage/CreateTemplateGalleryPage.test.tsx b/site/src/pages/CreateTemplateGalleryPage/CreateTemplateGalleryPage.test.tsx deleted file mode 100644 index 48f545ea1c3f2..0000000000000 --- a/site/src/pages/CreateTemplateGalleryPage/CreateTemplateGalleryPage.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { AppProviders } from "App"; -import { - MockTemplateExample, - MockTemplateExample2, -} from "testHelpers/entities"; -import { server } from "testHelpers/server"; -import { render, screen } from "@testing-library/react"; -import { RequireAuth } from "contexts/auth/RequireAuth"; -import { HttpResponse, http } from "msw"; -import { createMemoryRouter, RouterProvider } from "react-router"; -import CreateTemplateGalleryPage from "./CreateTemplateGalleryPage"; - -test("displays the scratch template", async () => { - server.use( - http.get("api/v2/templates/examples", () => { - return HttpResponse.json([ - MockTemplateExample, - MockTemplateExample2, - { - ...MockTemplateExample, - id: "scratch", - name: "Scratch", - description: "Create a template from scratch", - }, - ]); - }), - ); - - render( - - , - children: [ - { - path: "/starter-templates", - element: , - }, - ], - }, - ], - { initialEntries: ["/starter-templates"] }, - )} - /> - , - ); - - await screen.findByText(MockTemplateExample.name); - screen.getByText(MockTemplateExample2.name); - expect(screen.queryByText("Scratch")).toBeInTheDocument(); -}); diff --git a/site/src/pages/CreateTemplatePage/CreateTemplatePage.test.tsx b/site/src/pages/CreateTemplatePage/CreateTemplatePage.jest.tsx similarity index 100% rename from site/src/pages/CreateTemplatePage/CreateTemplatePage.test.tsx rename to site/src/pages/CreateTemplatePage/CreateTemplatePage.jest.tsx diff --git a/site/src/pages/CreateTokenPage/CreateTokenPage.test.tsx b/site/src/pages/CreateTokenPage/CreateTokenPage.jest.tsx similarity index 100% rename from site/src/pages/CreateTokenPage/CreateTokenPage.test.tsx rename to site/src/pages/CreateTokenPage/CreateTokenPage.jest.tsx diff --git a/site/src/pages/CreateUserPage/CreateUserPage.test.tsx b/site/src/pages/CreateUserPage/CreateUserPage.jest.tsx similarity index 100% rename from site/src/pages/CreateUserPage/CreateUserPage.test.tsx rename to site/src/pages/CreateUserPage/CreateUserPage.jest.tsx diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.jest.tsx similarity index 100% rename from site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx rename to site/src/pages/CreateWorkspacePage/CreateWorkspacePage.jest.tsx diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.test.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.jest.tsx similarity index 100% rename from site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.test.tsx rename to site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.jest.tsx diff --git a/site/src/pages/LoginPage/LoginPage.test.tsx b/site/src/pages/LoginPage/LoginPage.jest.tsx similarity index 100% rename from site/src/pages/LoginPage/LoginPage.test.tsx rename to site/src/pages/LoginPage/LoginPage.jest.tsx diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.jest.tsx similarity index 100% rename from site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx rename to site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.jest.tsx diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationRedirect.test.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationRedirect.jest.tsx similarity index 100% rename from site/src/pages/OrganizationSettingsPage/OrganizationRedirect.test.tsx rename to site/src/pages/OrganizationSettingsPage/OrganizationRedirect.jest.tsx diff --git a/site/src/pages/SetupPage/SetupPage.test.tsx b/site/src/pages/SetupPage/SetupPage.jest.tsx similarity index 100% rename from site/src/pages/SetupPage/SetupPage.test.tsx rename to site/src/pages/SetupPage/SetupPage.jest.tsx diff --git a/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.test.tsx b/site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.jest.tsx similarity index 100% rename from site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.test.tsx rename to site/src/pages/TemplatePage/TemplateEmbedPage/TemplateEmbedPage.jest.tsx diff --git a/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.test.tsx b/site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.jest.tsx similarity index 100% rename from site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.test.tsx rename to site/src/pages/TemplatePage/TemplateFilesPage/TemplateFilesPage.jest.tsx diff --git a/site/src/pages/TemplatePage/TemplateRedirectController.test.tsx b/site/src/pages/TemplatePage/TemplateRedirectController.jest.tsx similarity index 100% rename from site/src/pages/TemplatePage/TemplateRedirectController.test.tsx rename to site/src/pages/TemplatePage/TemplateRedirectController.jest.tsx diff --git a/site/src/pages/TemplatePage/useDeletionDialogState.test.ts b/site/src/pages/TemplatePage/useDeletionDialogState.jest.ts similarity index 100% rename from site/src/pages/TemplatePage/useDeletionDialogState.test.ts rename to site/src/pages/TemplatePage/useDeletionDialogState.jest.ts diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.jest.tsx similarity index 100% rename from site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx rename to site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.jest.tsx diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.jest.tsx similarity index 100% rename from site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx rename to site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.jest.tsx diff --git a/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.jest.tsx similarity index 100% rename from site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.test.tsx rename to site/src/pages/TemplateSettingsPage/TemplateVariablesPage/TemplateVariablesPage.jest.tsx diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.jest.tsx similarity index 100% rename from site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx rename to site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.jest.tsx diff --git a/site/src/pages/TemplateVersionPage/TemplateVersionPage.test.tsx b/site/src/pages/TemplateVersionPage/TemplateVersionPage.jest.tsx similarity index 100% rename from site/src/pages/TemplateVersionPage/TemplateVersionPage.test.tsx rename to site/src/pages/TemplateVersionPage/TemplateVersionPage.jest.tsx diff --git a/site/src/pages/TerminalPage/TerminalPage.test.tsx b/site/src/pages/TerminalPage/TerminalPage.jest.tsx similarity index 100% rename from site/src/pages/TerminalPage/TerminalPage.test.tsx rename to site/src/pages/TerminalPage/TerminalPage.jest.tsx diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountPage.test.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountPage.jest.tsx similarity index 100% rename from site/src/pages/UserSettingsPage/AccountPage/AccountPage.test.tsx rename to site/src/pages/UserSettingsPage/AccountPage/AccountPage.jest.tsx diff --git a/site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx b/site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.jest.tsx similarity index 100% rename from site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx rename to site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.jest.tsx diff --git a/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx b/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.jest.tsx similarity index 100% rename from site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx rename to site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.jest.tsx diff --git a/site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.test.tsx b/site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.jest.tsx similarity index 100% rename from site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.test.tsx rename to site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.jest.tsx diff --git a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.jest.tsx similarity index 100% rename from site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx rename to site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.jest.tsx diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.jest.tsx similarity index 100% rename from site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx rename to site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.jest.tsx diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.jest.tsx similarity index 100% rename from site/src/pages/WorkspacePage/WorkspacePage.test.tsx rename to site/src/pages/WorkspacePage/WorkspacePage.jest.tsx diff --git a/site/src/pages/WorkspacePage/WorkspaceScheduleControls.test.tsx b/site/src/pages/WorkspacePage/WorkspaceScheduleControls.jest.tsx similarity index 100% rename from site/src/pages/WorkspacePage/WorkspaceScheduleControls.test.tsx rename to site/src/pages/WorkspacePage/WorkspaceScheduleControls.jest.tsx diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.test.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.jest.tsx similarity index 100% rename from site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.test.tsx rename to site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPage.jest.tsx diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.jest.tsx similarity index 99% rename from site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.tsx rename to site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.jest.tsx index 68ffdac9e77d9..c34e2a6a99510 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.test.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceScheduleForm.jest.tsx @@ -144,17 +144,16 @@ describe("validationSchema", () => { expect(validate).toThrow(Language.errorTimezone); }); - it.each<[string]>(timeZones.map((zone) => [zone]))( - "validation passes for tz=%p", - (zone) => { + it("validation passes for all timezones", () => { + for (const zone of timeZones) { const values: WorkspaceScheduleFormValues = { ...valid, timezone: zone, }; const validate = () => validationSchema.validateSync(values); expect(validate).not.toThrow(); - }, - ); + } + }); it("allows a ttl of 7 days", () => { const values: WorkspaceScheduleFormValues = { diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.jest.tsx similarity index 100% rename from site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx rename to site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.jest.tsx diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.test.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.jest.tsx similarity index 100% rename from site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.test.tsx rename to site/src/pages/WorkspaceSettingsPage/WorkspaceSettingsPage.jest.tsx diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.jest.tsx similarity index 100% rename from site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx rename to site/src/pages/WorkspacesPage/WorkspacesPage.jest.tsx diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index b0ce392fa1feb..f858db66d1f20 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -507,10 +507,6 @@ export const MockAssignableSiteRoles = [ assignableRole(MockWorkspaceCreationBanRole, true), ]; -export const MockMemberPermissions = { - viewAuditLog: false, -}; - export const MockUserOwner: TypesGen.User = { id: "test-user", username: "TestUser", diff --git a/site/src/testHelpers/websockets.test.ts b/site/src/testHelpers/websockets.jest.ts similarity index 100% rename from site/src/testHelpers/websockets.test.ts rename to site/src/testHelpers/websockets.jest.ts diff --git a/site/src/utils/OneWayWebSocket.test.ts b/site/src/utils/OneWayWebSocket.jest.ts similarity index 100% rename from site/src/utils/OneWayWebSocket.test.ts rename to site/src/utils/OneWayWebSocket.jest.ts diff --git a/site/src/utils/events.test.ts b/site/src/utils/events.test.ts index 3f68afa07891b..f2fe5936f4c6b 100644 --- a/site/src/utils/events.test.ts +++ b/site/src/utils/events.test.ts @@ -2,17 +2,19 @@ import { dispatchCustomEvent, isCustomEvent } from "./events"; describe("events", () => { describe("dispatchCustomEvent", () => { - it("dispatch a custom event", (done) => { + it("dispatch a custom event", () => { const eventDetail = { title: "Event title" }; - window.addEventListener("eventType", (event) => { - if (isCustomEvent(event)) { - expect(event.detail).toEqual(eventDetail); - done(); - } - }); + return new Promise((resolve) => { + window.addEventListener("eventType", (event) => { + if (isCustomEvent(event)) { + expect(event.detail).toEqual(eventDetail); + resolve(); + } + }); - dispatchCustomEvent("eventType", eventDetail); + dispatchCustomEvent("eventType", eventDetail); + }); }); }); }); diff --git a/site/src/utils/formUtils.test.ts b/site/src/utils/formUtils.jest.ts similarity index 100% rename from site/src/utils/formUtils.test.ts rename to site/src/utils/formUtils.jest.ts diff --git a/site/src/utils/tar.test.ts b/site/src/utils/tar.jest.ts similarity index 100% rename from site/src/utils/tar.test.ts rename to site/src/utils/tar.jest.ts diff --git a/site/src/utils/tar.ts b/site/src/utils/tar.ts index 74b6e7b82530d..1f0ff7e1301c0 100644 --- a/site/src/utils/tar.ts +++ b/site/src/utils/tar.ts @@ -320,7 +320,7 @@ export class TarWriter { uid: 1000, gid: 1000, mode: fileType === TarFileTypeCodes.File ? 0o664 : 0o775, - mtime: ~~(Date.now() / 1000), + mtime: Math.trunc(Date.now() / 1000), user: "tarballjs", group: "tarballjs", ...opts, diff --git a/site/tsconfig.json b/site/tsconfig.json index 79b406d0f5c13..65d615f5b02f3 100644 --- a/site/tsconfig.json +++ b/site/tsconfig.json @@ -16,7 +16,7 @@ "skipLibCheck": true, "strict": true, "target": "es2020", - "types": ["jest", "node", "react", "react-dom", "vite/client"], + "types": ["node", "react", "react-dom", "vite/client", "vitest/globals"], "baseUrl": "src/" }, "include": ["**/*.ts", "**/*.tsx"], diff --git a/site/vite.config.mts b/site/vite.config.mts index d2da0a1a93752..fd656a11de86a 100644 --- a/site/vite.config.mts +++ b/site/vite.config.mts @@ -1,8 +1,9 @@ import * as path from "node:path"; import react from "@vitejs/plugin-react"; import { visualizer } from "rollup-plugin-visualizer"; -import { defineConfig, type PluginOption } from "vite"; +import type { PluginOption } from "vite"; import checker from "vite-plugin-checker"; +import { defineConfig } from "vitest/config"; const plugins: PluginOption[] = [ react(), @@ -120,6 +121,7 @@ export default defineConfig({ }, resolve: { alias: { + App: path.resolve(__dirname, "./src/App"), api: path.resolve(__dirname, "./src/api"), components: path.resolve(__dirname, "./src/components"), contexts: path.resolve(__dirname, "./src/contexts"), @@ -131,4 +133,11 @@ export default defineConfig({ utils: path.resolve(__dirname, "./src/utils"), }, }, + test: { + include: ["src/**/*.test.?(m)ts?(x)"], + globals: true, + environment: "jsdom", + setupFiles: ["@testing-library/jest-dom/vitest"], + silent: "passed-only", + }, }); From 77e2521fa0abff525f81174f3a107e5dfd89d5f7 Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 31 Oct 2025 15:10:36 -0800 Subject: [PATCH 048/255] feat: support workspace name in get workspace tool (#20474) This lets the LLM skip the list workspace step in some cases. Closes https://github.com/coder/internal/issues/1022 --- codersdk/toolsdk/toolsdk.go | 25 ++++++++++++++----------- codersdk/toolsdk/toolsdk_test.go | 30 +++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/codersdk/toolsdk/toolsdk.go b/codersdk/toolsdk/toolsdk.go index 802b319a5a6b1..1826729eed41a 100644 --- a/codersdk/toolsdk/toolsdk.go +++ b/codersdk/toolsdk/toolsdk.go @@ -317,13 +317,14 @@ type GetWorkspaceArgs struct { var GetWorkspace = Tool[GetWorkspaceArgs, codersdk.Workspace]{ Tool: aisdk.Tool{ Name: ToolNameGetWorkspace, - Description: `Get a workspace by ID. + Description: `Get a workspace by name or ID. This returns more data than list_workspaces to reduce token usage.`, Schema: aisdk.Schema{ Properties: map[string]any{ "workspace_id": map[string]any{ - "type": "string", + "type": "string", + "description": workspaceDescription, }, }, Required: []string{"workspace_id"}, @@ -332,7 +333,7 @@ This returns more data than list_workspaces to reduce token usage.`, Handler: func(ctx context.Context, deps Deps, args GetWorkspaceArgs) (codersdk.Workspace, error) { wsID, err := uuid.Parse(args.WorkspaceID) if err != nil { - return codersdk.Workspace{}, xerrors.New("workspace_id must be a valid UUID") + return namedWorkspace(ctx, deps.coderClient, NormalizeWorkspaceInput(args.WorkspaceID)) } return deps.coderClient.Workspace(ctx, wsID) }, @@ -1432,7 +1433,7 @@ var WorkspaceLS = Tool[WorkspaceLSArgs, WorkspaceLSResponse]{ Properties: map[string]any{ "workspace": map[string]any{ "type": "string", - "description": workspaceDescription, + "description": workspaceAgentDescription, }, "path": map[string]any{ "type": "string", @@ -1489,7 +1490,7 @@ var WorkspaceReadFile = Tool[WorkspaceReadFileArgs, WorkspaceReadFileResponse]{ Properties: map[string]any{ "workspace": map[string]any{ "type": "string", - "description": workspaceDescription, + "description": workspaceAgentDescription, }, "path": map[string]any{ "type": "string", @@ -1566,7 +1567,7 @@ content you are trying to write, then re-encode it properly. Properties: map[string]any{ "workspace": map[string]any{ "type": "string", - "description": workspaceDescription, + "description": workspaceAgentDescription, }, "path": map[string]any{ "type": "string", @@ -1614,7 +1615,7 @@ var WorkspaceEditFile = Tool[WorkspaceEditFileArgs, codersdk.Response]{ Properties: map[string]any{ "workspace": map[string]any{ "type": "string", - "description": workspaceDescription, + "description": workspaceAgentDescription, }, "path": map[string]any{ "type": "string", @@ -1681,7 +1682,7 @@ var WorkspaceEditFiles = Tool[WorkspaceEditFilesArgs, codersdk.Response]{ Properties: map[string]any{ "workspace": map[string]any{ "type": "string", - "description": workspaceDescription, + "description": workspaceAgentDescription, }, "files": map[string]any{ "type": "array", @@ -1755,7 +1756,7 @@ var WorkspacePortForward = Tool[WorkspacePortForwardArgs, WorkspacePortForwardRe Properties: map[string]any{ "workspace": map[string]any{ "type": "string", - "description": workspaceDescription, + "description": workspaceAgentDescription, }, "port": map[string]any{ "type": "number", @@ -1812,7 +1813,7 @@ var WorkspaceListApps = Tool[WorkspaceListAppsArgs, WorkspaceListAppsResponse]{ Properties: map[string]any{ "workspace": map[string]any{ "type": "string", - "description": workspaceDescription, + "description": workspaceAgentDescription, }, }, Required: []string{"workspace"}, @@ -2199,7 +2200,9 @@ func newAgentConn(ctx context.Context, client *codersdk.Client, workspace string return conn, nil } -const workspaceDescription = "The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used." +const workspaceDescription = "The workspace ID or name in the format [owner/]workspace. If an owner is not specified, the authenticated user is used." + +const workspaceAgentDescription = "The workspace name in the format [owner/]workspace[.agent]. If an owner is not specified, the authenticated user is used." func taskIDDescription(action string) string { return fmt.Sprintf("ID or workspace identifier in the format [owner/]workspace[.agent] for the task to %s. If an owner is not specified, the authenticated user is used.", action) diff --git a/codersdk/toolsdk/toolsdk_test.go b/codersdk/toolsdk/toolsdk_test.go index 749105f5b85ed..acd7dba8c09d7 100644 --- a/codersdk/toolsdk/toolsdk_test.go +++ b/codersdk/toolsdk/toolsdk_test.go @@ -126,12 +126,32 @@ func TestTools(t *testing.T) { t.Run("GetWorkspace", func(t *testing.T) { tb, err := toolsdk.NewDeps(memberClient) require.NoError(t, err) - result, err := testTool(t, toolsdk.GetWorkspace, tb, toolsdk.GetWorkspaceArgs{ - WorkspaceID: r.Workspace.ID.String(), - }) - require.NoError(t, err) - require.Equal(t, r.Workspace.ID, result.ID, "expected the workspace ID to match") + tests := []struct { + name string + workspace string + }{ + { + name: "ByID", + workspace: r.Workspace.ID.String(), + }, + { + name: "ByName", + workspace: r.Workspace.Name, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result, err := testTool(t, toolsdk.GetWorkspace, tb, toolsdk.GetWorkspaceArgs{ + WorkspaceID: tt.workspace, + }) + require.NoError(t, err) + require.Equal(t, r.Workspace.ID, result.ID, "expected the workspace ID to match") + }) + } }) t.Run("ListTemplates", func(t *testing.T) { From a899fc57a6314a9f6988db627228a0fcfededb8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:17:38 +0000 Subject: [PATCH 049/255] chore: bump @types/node from 20.19.19 to 20.19.24 in /offlinedocs (#20617) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.19.19 to 20.19.24.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=20.19.19&new-version=20.19.24)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- offlinedocs/package.json | 2 +- offlinedocs/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/offlinedocs/package.json b/offlinedocs/package.json index 26073286ddb65..988bf3b6790a2 100644 --- a/offlinedocs/package.json +++ b/offlinedocs/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@types/lodash": "4.17.20", - "@types/node": "20.19.19", + "@types/node": "20.19.24", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@types/sanitize-html": "2.16.0", diff --git a/offlinedocs/pnpm-lock.yaml b/offlinedocs/pnpm-lock.yaml index 7c4466814364c..bf043ee4e30e2 100644 --- a/offlinedocs/pnpm-lock.yaml +++ b/offlinedocs/pnpm-lock.yaml @@ -62,8 +62,8 @@ importers: specifier: 4.17.20 version: 4.17.20 '@types/node': - specifier: 20.19.19 - version: 20.19.19 + specifier: 20.19.24 + version: 20.19.24 '@types/react': specifier: 18.3.12 version: 18.3.12 @@ -547,8 +547,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@20.19.19': - resolution: {integrity: sha512-pb1Uqj5WJP7wrcbLU7Ru4QtA0+3kAXrkutGiD26wUKzSMgNNaPARTUDQmElUXp64kh3cWdou3Q0C7qwwxqSFmg==} + '@types/node@20.19.24': + resolution: {integrity: sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -3074,7 +3074,7 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@20.19.19': + '@types/node@20.19.24': dependencies: undici-types: 6.21.0 From fb785d3524731830f0bc2eaf0381c29ba224482a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:18:01 +0000 Subject: [PATCH 050/255] chore: bump next from 15.5.4 to 15.5.6 in /offlinedocs (#20618) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [next](https://github.com/vercel/next.js) from 15.5.4 to 15.5.6.
Release notes

Sourced from next's releases.

v15.5.6

[!NOTE]
This release is backporting bug fixes. It does not include all pending features/changes on canary.

Core Changes

  • Turbopack: don't define process.cwd() in node_modules #83452

Credits

Huge thanks to @​mischnic for helping!

v15.5.5

[!NOTE]
This release is backporting bug fixes. It does not include all pending features/changes on canary.

Core Changes

  • Split code-frame into separate compiled package (#84238)
  • Add deprecation warning to Runtime config (#84650)
  • fix: unstable_cache should perform blocking revalidation during ISR revalidation (#84716)
  • feat: experimental.middlewareClientMaxBodySize body cloning limit (#84722)
  • fix: missing next/link types with typedRoutes (#84779)

Misc Changes

  • docs: early October improvements and fixes (#84334)

Credits

Huge thanks to @​devjiwonchoi, @​ztanner, and @​icyJoseph for helping!

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=next&package-manager=npm_and_yarn&previous-version=15.5.4&new-version=15.5.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- offlinedocs/package.json | 2 +- offlinedocs/pnpm-lock.yaml | 120 ++++++++++++++++++------------------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/offlinedocs/package.json b/offlinedocs/package.json index 988bf3b6790a2..4e52395036dd4 100644 --- a/offlinedocs/package.json +++ b/offlinedocs/package.json @@ -20,7 +20,7 @@ "framer-motion": "^10.18.0", "front-matter": "4.0.2", "lodash": "4.17.21", - "next": "15.5.4", + "next": "15.5.6", "react": "18.3.1", "react-dom": "18.3.1", "react-icons": "4.12.0", diff --git a/offlinedocs/pnpm-lock.yaml b/offlinedocs/pnpm-lock.yaml index bf043ee4e30e2..1b49cbd1518db 100644 --- a/offlinedocs/pnpm-lock.yaml +++ b/offlinedocs/pnpm-lock.yaml @@ -34,8 +34,8 @@ importers: specifier: 4.17.21 version: 4.17.21 next: - specifier: 15.5.4 - version: 15.5.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 15.5.6 + version: 15.5.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -175,8 +175,8 @@ packages: '@emnapi/core@1.5.0': resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} - '@emnapi/runtime@1.5.0': - resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + '@emnapi/runtime@1.6.0': + resolution: {integrity: sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} @@ -428,56 +428,56 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@next/env@15.5.4': - resolution: {integrity: sha512-27SQhYp5QryzIT5uO8hq99C69eLQ7qkzkDPsk3N+GuS2XgOgoYEeOav7Pf8Tn4drECOVDsDg8oj+/DVy8qQL2A==} + '@next/env@15.5.6': + resolution: {integrity: sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q==} '@next/eslint-plugin-next@14.2.33': resolution: {integrity: sha512-DQTJFSvlB+9JilwqMKJ3VPByBNGxAGFTfJ7BuFj25cVcbBy7jm88KfUN+dngM4D3+UxZ8ER2ft+WH9JccMvxyg==} - '@next/swc-darwin-arm64@15.5.4': - resolution: {integrity: sha512-nopqz+Ov6uvorej8ndRX6HlxCYWCO3AHLfKK2TYvxoSB2scETOcfm/HSS3piPqc3A+MUgyHoqE6je4wnkjfrOA==} + '@next/swc-darwin-arm64@15.5.6': + resolution: {integrity: sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.5.4': - resolution: {integrity: sha512-QOTCFq8b09ghfjRJKfb68kU9k2K+2wsC4A67psOiMn849K9ZXgCSRQr0oVHfmKnoqCbEmQWG1f2h1T2vtJJ9mA==} + '@next/swc-darwin-x64@15.5.6': + resolution: {integrity: sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.5.4': - resolution: {integrity: sha512-eRD5zkts6jS3VfE/J0Kt1VxdFqTnMc3QgO5lFE5GKN3KDI/uUpSyK3CjQHmfEkYR4wCOl0R0XrsjpxfWEA++XA==} + '@next/swc-linux-arm64-gnu@15.5.6': + resolution: {integrity: sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.5.4': - resolution: {integrity: sha512-TOK7iTxmXFc45UrtKqWdZ1shfxuL4tnVAOuuJK4S88rX3oyVV4ZkLjtMT85wQkfBrOOvU55aLty+MV8xmcJR8A==} + '@next/swc-linux-arm64-musl@15.5.6': + resolution: {integrity: sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.5.4': - resolution: {integrity: sha512-7HKolaj+481FSW/5lL0BcTkA4Ueam9SPYWyN/ib/WGAFZf0DGAN8frNpNZYFHtM4ZstrHZS3LY3vrwlIQfsiMA==} + '@next/swc-linux-x64-gnu@15.5.6': + resolution: {integrity: sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.5.4': - resolution: {integrity: sha512-nlQQ6nfgN0nCO/KuyEUwwOdwQIGjOs4WNMjEUtpIQJPR2NUfmGpW2wkJln1d4nJ7oUzd1g4GivH5GoEPBgfsdw==} + '@next/swc-linux-x64-musl@15.5.6': + resolution: {integrity: sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.5.4': - resolution: {integrity: sha512-PcR2bN7FlM32XM6eumklmyWLLbu2vs+D7nJX8OAIoWy69Kef8mfiN4e8TUv2KohprwifdpFKPzIP1njuCjD0YA==} + '@next/swc-win32-arm64-msvc@15.5.6': + resolution: {integrity: sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.5.4': - resolution: {integrity: sha512-1ur2tSHZj8Px/KMAthmuI9FMp/YFusMMGoRNJaRZMOlSkgvLjzosSdQI0cJAKogdHl3qXUQKL9MGaYvKwA7DXg==} + '@next/swc-win32-x64-msvc@15.5.6': + resolution: {integrity: sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -890,8 +890,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001746: - resolution: {integrity: sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==} + caniuse-lite@1.0.30001752: + resolution: {integrity: sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1031,8 +1031,8 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - detect-libc@2.1.1: - resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} detect-node-es@1.1.0: @@ -1906,8 +1906,8 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next@15.5.4: - resolution: {integrity: sha512-xH4Yjhb82sFYQfY3vbkJfgSDgXvBB6a8xPs9i35k6oZJRoQRihZH+4s9Yo2qsWpzBmZ3lPXaJ2KPXLfkvW4LnA==} + next@15.5.6: + resolution: {integrity: sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -2238,8 +2238,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true @@ -2718,7 +2718,7 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.5.0': + '@emnapi/runtime@1.6.0': dependencies: tslib: 2.8.1 optional: true @@ -2938,7 +2938,7 @@ snapshots: '@img/sharp-wasm32@0.34.4': dependencies: - '@emnapi/runtime': 1.5.0 + '@emnapi/runtime': 1.6.0 optional: true '@img/sharp-win32-arm64@0.34.4': @@ -2976,38 +2976,38 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.5.0 - '@emnapi/runtime': 1.5.0 + '@emnapi/runtime': 1.6.0 '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@15.5.4': {} + '@next/env@15.5.6': {} '@next/eslint-plugin-next@14.2.33': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@15.5.4': + '@next/swc-darwin-arm64@15.5.6': optional: true - '@next/swc-darwin-x64@15.5.4': + '@next/swc-darwin-x64@15.5.6': optional: true - '@next/swc-linux-arm64-gnu@15.5.4': + '@next/swc-linux-arm64-gnu@15.5.6': optional: true - '@next/swc-linux-arm64-musl@15.5.4': + '@next/swc-linux-arm64-musl@15.5.6': optional: true - '@next/swc-linux-x64-gnu@15.5.4': + '@next/swc-linux-x64-gnu@15.5.6': optional: true - '@next/swc-linux-x64-musl@15.5.4': + '@next/swc-linux-x64-musl@15.5.6': optional: true - '@next/swc-win32-arm64-msvc@15.5.4': + '@next/swc-win32-arm64-msvc@15.5.6': optional: true - '@next/swc-win32-x64-msvc@15.5.4': + '@next/swc-win32-x64-msvc@15.5.6': optional: true '@nodelib/fs.scandir@2.1.5': @@ -3172,7 +3172,7 @@ snapshots: fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.2 + semver: 7.7.3 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -3445,7 +3445,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001746: {} + caniuse-lite@1.0.30001752: {} ccount@2.0.1: {} @@ -3574,7 +3574,7 @@ snapshots: dequal@2.0.3: {} - detect-libc@2.1.1: + detect-libc@2.1.2: optional: true detect-node-es@1.1.0: {} @@ -4282,7 +4282,7 @@ snapshots: is-bun-module@2.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 is-callable@1.2.7: {} @@ -4854,24 +4854,24 @@ snapshots: natural-compare@1.4.0: {} - next@15.5.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.5.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 15.5.4 + '@next/env': 15.5.6 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001746 + caniuse-lite: 1.0.30001752 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.6(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 15.5.4 - '@next/swc-darwin-x64': 15.5.4 - '@next/swc-linux-arm64-gnu': 15.5.4 - '@next/swc-linux-arm64-musl': 15.5.4 - '@next/swc-linux-x64-gnu': 15.5.4 - '@next/swc-linux-x64-musl': 15.5.4 - '@next/swc-win32-arm64-msvc': 15.5.4 - '@next/swc-win32-x64-msvc': 15.5.4 + '@next/swc-darwin-arm64': 15.5.6 + '@next/swc-darwin-x64': 15.5.6 + '@next/swc-linux-arm64-gnu': 15.5.6 + '@next/swc-linux-arm64-musl': 15.5.6 + '@next/swc-linux-x64-gnu': 15.5.6 + '@next/swc-linux-x64-musl': 15.5.6 + '@next/swc-win32-arm64-msvc': 15.5.6 + '@next/swc-win32-x64-msvc': 15.5.6 sharp: 0.34.4 transitivePeerDependencies: - '@babel/core' @@ -5258,7 +5258,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.2: {} + semver@7.7.3: {} set-function-length@1.2.2: dependencies: @@ -5285,8 +5285,8 @@ snapshots: sharp@0.34.4: dependencies: '@img/colour': 1.0.0 - detect-libc: 2.1.1 - semver: 7.7.2 + detect-libc: 2.1.2 + semver: 7.7.3 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.4 '@img/sharp-darwin-x64': 0.34.4 From c4e9749146dae7be2b34c1f3b42c5600ab82ade0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:18:46 +0000 Subject: [PATCH 051/255] chore: bump the radix group across 1 directory with 12 updates (#20614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the radix group with 12 updates in the /site directory: | Package | From | To | | --- | --- | --- | | [@radix-ui/react-avatar](https://github.com/radix-ui/primitives) | `1.1.2` | `1.1.10` | | [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives) | `1.1.4` | `1.3.3` | | [@radix-ui/react-collapsible](https://github.com/radix-ui/primitives) | `1.1.2` | `1.1.12` | | [@radix-ui/react-dialog](https://github.com/radix-ui/primitives) | `1.1.4` | `1.1.15` | | [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives) | `2.1.4` | `2.1.16` | | [@radix-ui/react-label](https://github.com/radix-ui/primitives) | `2.1.0` | `2.1.7` | | [@radix-ui/react-popover](https://github.com/radix-ui/primitives) | `1.1.5` | `1.1.15` | | [@radix-ui/react-radio-group](https://github.com/radix-ui/primitives) | `1.2.3` | `1.3.8` | | [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives) | `1.2.3` | `1.2.10` | | [@radix-ui/react-slider](https://github.com/radix-ui/primitives) | `1.2.2` | `1.3.6` | | [@radix-ui/react-switch](https://github.com/radix-ui/primitives) | `1.1.1` | `1.2.6` | | [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives) | `1.1.7` | `1.2.8` | Updates `@radix-ui/react-avatar` from 1.1.2 to 1.1.10
Commits

Updates `@radix-ui/react-checkbox` from 1.1.4 to 1.3.3
Commits

Updates `@radix-ui/react-collapsible` from 1.1.2 to 1.1.12
Commits

Updates `@radix-ui/react-dialog` from 1.1.4 to 1.1.15
Commits

Updates `@radix-ui/react-dropdown-menu` from 2.1.4 to 2.1.16
Commits

Updates `@radix-ui/react-label` from 2.1.0 to 2.1.7
Commits
Maintainer changes

This version was pushed to npm by chancestrickland, a new releaser for @​radix-ui/react-label since your current version.


Updates `@radix-ui/react-popover` from 1.1.5 to 1.1.15
Commits

Updates `@radix-ui/react-radio-group` from 1.2.3 to 1.3.8
Commits

Updates `@radix-ui/react-scroll-area` from 1.2.3 to 1.2.10
Commits

Updates `@radix-ui/react-slider` from 1.2.2 to 1.3.6
Commits

Updates `@radix-ui/react-switch` from 1.1.1 to 1.2.6
Commits

Updates `@radix-ui/react-tooltip` from 1.1.7 to 1.2.8
Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 24 +- site/pnpm-lock.yaml | 987 ++++++++++---------------------------------- 2 files changed, 236 insertions(+), 775 deletions(-) diff --git a/site/package.json b/site/package.json index 43281f5453726..4c6ff500badab 100644 --- a/site/package.json +++ b/site/package.json @@ -51,21 +51,21 @@ "@mui/system": "5.18.0", "@mui/utils": "5.17.1", "@mui/x-tree-view": "7.29.10", - "@radix-ui/react-avatar": "1.1.2", - "@radix-ui/react-checkbox": "1.1.4", - "@radix-ui/react-collapsible": "1.1.2", - "@radix-ui/react-dialog": "1.1.4", - "@radix-ui/react-dropdown-menu": "2.1.4", - "@radix-ui/react-label": "2.1.0", - "@radix-ui/react-popover": "1.1.5", - "@radix-ui/react-radio-group": "1.2.3", - "@radix-ui/react-scroll-area": "1.2.3", + "@radix-ui/react-avatar": "1.1.10", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", - "@radix-ui/react-slider": "1.2.2", + "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-switch": "1.1.1", - "@radix-ui/react-tooltip": "1.1.7", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tooltip": "1.2.8", "@tanstack/react-query-devtools": "5.77.0", "@xterm/addon-canvas": "0.7.0", "@xterm/addon-fit": "0.10.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index b70a5b755342d..e880ff0bd76fc 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -68,32 +68,32 @@ importers: specifier: 7.29.10 version: 7.29.10(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-avatar': - specifier: 1.1.2 - version: 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.1.10 + version: 1.1.10(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-checkbox': - specifier: 1.1.4 - version: 1.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.3.3 + version: 1.3.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-collapsible': - specifier: 1.1.2 - version: 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.1.12 + version: 1.1.12(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-dialog': - specifier: 1.1.4 - version: 1.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.1.15 + version: 1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-dropdown-menu': - specifier: 2.1.4 - version: 2.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 2.1.16 + version: 2.1.16(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-label': - specifier: 2.1.0 - version: 2.1.0(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 2.1.7 + version: 2.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-popover': - specifier: 1.1.5 - version: 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.1.15 + version: 1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-radio-group': - specifier: 1.2.3 - version: 1.2.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.3.8 + version: 1.3.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-scroll-area': - specifier: 1.2.3 - version: 1.2.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.2.10 + version: 1.2.10(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-select': specifier: 2.2.6 version: 2.2.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -101,17 +101,17 @@ importers: specifier: 1.1.7 version: 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-slider': - specifier: 1.2.2 - version: 1.2.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.3.6 + version: 1.3.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-slot': specifier: 1.2.3 version: 1.2.3(@types/react@19.1.17)(react@19.1.1) '@radix-ui/react-switch': - specifier: 1.1.1 - version: 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.2.6 + version: 1.2.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-tooltip': - specifier: 1.1.7 - version: 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.2.8 + version: 1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@tanstack/react-query-devtools': specifier: 5.77.0 version: 5.77.0(@tanstack/react-query@5.77.0(react@19.1.1))(react@19.1.1) @@ -1161,8 +1161,8 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==, tarball: https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, tarball: https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/eslintrc@2.1.4': @@ -1173,24 +1173,12 @@ packages: resolution: {integrity: sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==, tarball: https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@floating-ui/core@1.6.9': - resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==, tarball: https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz} - '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==, tarball: https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz} - '@floating-ui/dom@1.6.13': - resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==, tarball: https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz} - '@floating-ui/dom@1.7.4': resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==, tarball: https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz} - '@floating-ui/react-dom@2.1.2': - resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==, tarball: https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - '@floating-ui/react-dom@2.1.6': resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==, tarball: https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz} peerDependencies: @@ -1200,9 +1188,6 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==, tarball: https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz} - '@floating-ui/utils@0.2.9': - resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==, tarball: https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz} - '@fontsource-variable/inter@5.1.1': resolution: {integrity: sha512-OpXFTmiH6tHkYijMvQTycFKBLK4X+SRV6tet1m4YOUH7SzIIlMqDja+ocDtiCA72UthBH/vF+3ZtlMr2rN/wIw==, tarball: https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.1.1.tgz} @@ -1688,34 +1673,12 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==, tarball: https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz} - '@radix-ui/number@1.1.0': - resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==, tarball: https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz} - '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==, tarball: https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz} - '@radix-ui/primitive@1.1.0': - resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==, tarball: https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz} - - '@radix-ui/primitive@1.1.1': - resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==, tarball: https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz} - '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==, tarball: https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz} - '@radix-ui/react-arrow@1.1.1': - resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==, tarball: https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-arrow@1.1.7': resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==, tarball: https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz} peerDependencies: @@ -1729,34 +1692,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-avatar@1.1.2': - resolution: {integrity: sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==, tarball: https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.2.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-checkbox@1.1.4': - resolution: {integrity: sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==, tarball: https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collapsible@1.1.2': - resolution: {integrity: sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==, tarball: https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz} + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==, tarball: https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1768,8 +1705,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.1': - resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==, tarball: https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz} + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==, tarball: https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1781,8 +1718,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.2': - resolution: {integrity: sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==, tarball: https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz} + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==, tarball: https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1807,15 +1744,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-compose-refs@1.1.0': - resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==, tarball: https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-compose-refs@1.1.1': resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==, tarball: https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz} peerDependencies: @@ -1834,15 +1762,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-context@1.1.1': - resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==, tarball: https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-context@1.1.2': resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==, tarball: https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz} peerDependencies: @@ -1852,8 +1771,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dialog@1.1.4': - resolution: {integrity: sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==, tarball: https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz} + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==, tarball: https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1865,15 +1784,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-direction@1.1.0': - resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==, tarball: https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-direction@1.1.1': resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==, tarball: https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz} peerDependencies: @@ -1896,21 +1806,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dismissable-layer@1.1.3': - resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==, tarball: https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.4': - resolution: {integrity: sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==, tarball: https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz} + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==, tarball: https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1922,28 +1819,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.4': - resolution: {integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==, tarball: https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.4.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-guards@1.1.1': - resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==, tarball: https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-focus-guards@1.1.3': resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==, tarball: https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz} peerDependencies: @@ -1953,19 +1828,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.1.1': - resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==, tarball: https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-focus-scope@1.1.7': resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==, tarball: https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz} peerDependencies: @@ -1997,21 +1859,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-label@2.1.0': - resolution: {integrity: sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==, tarball: https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-menu@2.1.4': - resolution: {integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==, tarball: https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.4.tgz} + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==, tarball: https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2023,8 +1872,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popover@1.1.5': - resolution: {integrity: sha512-YXkTAftOIW2Bt3qKH8vYr6n9gCkVrvyvfiTObVjoHVTHnNj26rmvO87IKa3VgtgCjb8FAQ6qOjNViwl+9iIzlg==, tarball: https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.5.tgz} + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==, tarball: https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2036,8 +1885,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.2.1': - resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==, tarball: https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz} + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==, tarball: https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2062,19 +1911,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.3': - resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==, tarball: https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-portal@1.1.9': resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==, tarball: https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz} peerDependencies: @@ -2088,21 +1924,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-presence@1.1.2': - resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==, tarball: https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.0.0': - resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==, tarball: https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz} + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==, tarball: https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2127,19 +1950,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.0.2': - resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==, tarball: https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-primitive@2.1.3': resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==, tarball: https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz} peerDependencies: @@ -2153,21 +1963,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-radio-group@1.2.3': - resolution: {integrity: sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==, tarball: https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.3.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-roving-focus@1.1.1': - resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==, tarball: https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz} + '@radix-ui/react-radio-group@1.3.8': + resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==, tarball: https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2179,8 +1976,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.2': - resolution: {integrity: sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==, tarball: https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz} + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==, tarball: https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2192,8 +1989,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.2.3': - resolution: {integrity: sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==, tarball: https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.3.tgz} + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==, tarball: https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2231,8 +2028,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slider@1.2.2': - resolution: {integrity: sha512-sNlU06ii1/ZcbHf8I9En54ZPW0Vil/yPVg4vQMcFNjrIx51jsHbFl1HYHQvCIWJSr1q0ZmA+iIs/ZTv8h7HHSA==, tarball: https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.2.tgz} + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==, tarball: https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2244,15 +2041,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.1.0': - resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==, tarball: https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-slot@1.1.1': resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==, tarball: https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz} peerDependencies: @@ -2262,15 +2050,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-slot@1.1.2': - resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==, tarball: https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==, tarball: https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz} peerDependencies: @@ -2280,8 +2059,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-switch@1.1.1': - resolution: {integrity: sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==, tarball: https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.1.tgz} + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==, tarball: https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2293,8 +2072,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-tooltip@1.1.7': - resolution: {integrity: sha512-ss0s80BC0+g0+Zc53MvilcnTYSOi4mSuFWBPYPuTOFGjx+pUU+ZrmamMNwS56t8MTFlniA5ocjd4jYm/CdhbOg==, tarball: https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.7.tgz} + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==, tarball: https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2306,15 +2085,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-use-callback-ref@1.1.0': - resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-callback-ref@1.1.1': resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==, tarball: https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz} peerDependencies: @@ -2324,15 +2094,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-controllable-state@1.1.0': - resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-controllable-state@1.2.2': resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==, tarball: https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz} peerDependencies: @@ -2351,8 +2112,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.0': - resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz} + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==, tarball: https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2360,8 +2121,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.1': - resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==, tarball: https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz} + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==, tarball: https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2387,15 +2148,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-previous@1.1.0': - resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==, tarball: https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-previous@1.1.1': resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==, tarball: https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz} peerDependencies: @@ -2405,15 +2157,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-rect@1.1.0': - resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==, tarball: https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-rect@1.1.1': resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==, tarball: https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz} peerDependencies: @@ -2423,15 +2166,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-size@1.1.0': - resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==, tarball: https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-size@1.1.1': resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==, tarball: https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz} peerDependencies: @@ -2441,19 +2175,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-visually-hidden@1.1.1': - resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==, tarball: https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-visually-hidden@1.2.3': resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==, tarball: https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz} peerDependencies: @@ -2467,9 +2188,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/rect@1.1.0': - resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==, tarball: https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz} - '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==, tarball: https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz} @@ -3278,10 +2996,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, tarball: https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz} - aria-hidden@1.2.4: - resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==, tarball: https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz} - engines: {node: '>=10'} - aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==, tarball: https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz} engines: {node: '>=10'} @@ -5723,16 +5437,6 @@ packages: '@types/react': optional: true - react-remove-scroll@2.6.3: - resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==, tarball: https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - react-remove-scroll@2.7.1: resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==, tarball: https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz} engines: {node: '>=10'} @@ -6526,6 +6230,11 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==, tarball: https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, tarball: https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz} @@ -7479,7 +7188,7 @@ snapshots: eslint-visitor-keys: 3.4.3 optional: true - '@eslint-community/regexpp@4.12.1': + '@eslint-community/regexpp@4.12.2': optional: true '@eslint/eslintrc@2.1.4': @@ -7500,30 +7209,15 @@ snapshots: '@eslint/js@8.52.0': optional: true - '@floating-ui/core@1.6.9': - dependencies: - '@floating-ui/utils': 0.2.9 - '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 - '@floating-ui/dom@1.6.13': - dependencies: - '@floating-ui/core': 1.6.9 - '@floating-ui/utils': 0.2.9 - '@floating-ui/dom@1.7.4': dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@floating-ui/dom': 1.6.13 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - '@floating-ui/react-dom@2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@floating-ui/dom': 1.7.4 @@ -7532,8 +7226,6 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@floating-ui/utils@0.2.9': {} - '@fontsource-variable/inter@5.1.1': {} '@fontsource/fira-code@5.2.7': {} @@ -8114,26 +7806,11 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@radix-ui/number@1.1.0': {} - '@radix-ui/number@1.1.1': {} - '@radix-ui/primitive@1.1.0': {} - - '@radix-ui/primitive@1.1.1': {} - '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-arrow@1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 @@ -8142,68 +7819,45 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-avatar@1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-checkbox@1.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-collapsible@1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-collection@1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-collection@1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: @@ -8222,12 +7876,6 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-compose-refs@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-compose-refs@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 @@ -8240,32 +7888,26 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-context@1.1.1(@types/react@19.1.17)(react@19.1.1)': - dependencies: - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-context@1.1.2(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-dialog@1.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) aria-hidden: 1.2.6 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -8274,12 +7916,6 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-direction@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-direction@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 @@ -8299,70 +7935,27 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-dismissable-layer@1.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-dropdown-menu@2.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-menu': 2.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-focus-guards@1.1.1(@types/react@19.1.17)(react@19.1.1)': - dependencies: - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) @@ -8388,33 +7981,33 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-label@2.1.0(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-menu@2.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) aria-hidden: 1.2.6 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -8423,43 +8016,25 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-popover@1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) - aria-hidden: 1.2.4 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.6.3(@types/react@19.1.17)(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-popper@1.2.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-arrow': 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-rect': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/rect': 1.1.0 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) + aria-hidden: 1.2.6 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.17)(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) @@ -8482,16 +8057,6 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-portal@1.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -8502,19 +8067,10 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-presence@1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-primitive@2.0.0(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/react-slot': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: @@ -8530,15 +8086,6 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-primitive@2.0.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-slot': 1.1.2(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) @@ -8548,69 +8095,52 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-radio-group@1.2.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-roving-focus': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-roving-focus@1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-scroll-area@1.2.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/number': 1.1.0 - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: @@ -8655,32 +8185,25 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-slider@1.2.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/number': 1.1.0 - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-slot@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-slot@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) @@ -8688,13 +8211,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-slot@1.1.2(@types/react@19.1.17)(react@19.1.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-slot@1.2.3(@types/react@19.1.17)(react@19.1.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) @@ -8702,60 +8218,47 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-switch@1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.0(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.17)(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-tooltip@1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-popper': 1.2.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.17)(react@19.1.1)': dependencies: '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.17)(react@19.1.1) @@ -8771,13 +8274,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) @@ -8785,19 +8281,20 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 + use-sync-external-store: 1.6.0(react@19.1.1) optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-use-previous@1.1.0(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 optionalDependencies: @@ -8809,13 +8306,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-use-rect@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - '@radix-ui/rect': 1.1.0 - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: '@radix-ui/rect': 1.1.1 @@ -8823,13 +8313,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-use-size@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-use-size@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) @@ -8837,15 +8320,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -8855,8 +8329,6 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/rect@1.1.0': {} - '@radix-ui/rect@1.1.1': {} '@rolldown/pluginutils@1.0.0-beta.38': {} @@ -9651,10 +9123,6 @@ snapshots: argparse@2.0.1: {} - aria-hidden@1.2.4: - dependencies: - tslib: 2.8.1 - aria-hidden@1.2.6: dependencies: tslib: 2.8.1 @@ -9977,7 +9445,7 @@ snapshots: cmdk@1.0.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: - '@radix-ui/react-dialog': 1.1.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 @@ -10452,7 +9920,7 @@ snapshots: eslint@8.52.0: dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@8.52.0) - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/regexpp': 4.12.2 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.52.0 '@humanwhocodes/config-array': 0.11.14 @@ -12682,17 +12150,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - react-remove-scroll@2.6.3(@types/react@19.1.17)(react@19.1.1): - dependencies: - react: 19.1.1 - react-remove-scroll-bar: 2.3.8(@types/react@19.1.17)(react@19.1.1) - react-style-singleton: 2.2.3(@types/react@19.1.17)(react@19.1.1) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.1.17)(react@19.1.1) - use-sidecar: 1.1.3(@types/react@19.1.17)(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - react-remove-scroll@2.7.1(@types/react@19.1.17)(react@19.1.1): dependencies: react: 19.1.1 @@ -13560,6 +13017,10 @@ snapshots: dependencies: react: 19.1.1 + use-sync-external-store@1.6.0(react@19.1.1): + dependencies: + react: 19.1.1 + util-deprecate@1.0.2: {} utils-merge@1.0.1: {} From 979df63788b5cb38503e11da26634abb8254be65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:20:41 +0000 Subject: [PATCH 052/255] chore: bump the vite group across 1 directory with 3 updates (#20616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the vite group with 3 updates in the /site directory: [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react), [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). Updates `@vitejs/plugin-react` from 5.0.4 to 5.1.0
Release notes

Sourced from @​vitejs/plugin-react's releases.

plugin-react@5.1.0

Add @vitejs/plugin-react/preamble virtual module for SSR HMR (#890)

SSR applications can now initialize HMR runtime by importing @vitejs/plugin-react/preamble at the top of their client entry instead of manually calling transformIndexHtml. This simplifies SSR setup for applications that don't use the transformIndexHtml API.

Fix raw Rolldown support for Rolldown 1.0.0-beta.44+ (#930)

Rolldown 1.0.0-beta.44+ removed the top-level jsx option in favor of transform.jsx. This plugin now uses the transform.jsx option to support Rolldown 1.0.0-beta.44+.

Changelog

Sourced from @​vitejs/plugin-react's changelog.

5.1.0 (2025-10-24)

Add @vitejs/plugin-react/preamble virtual module for SSR HMR (#890)

SSR applications can now initialize HMR runtime by importing @vitejs/plugin-react/preamble at the top of their client entry instead of manually calling transformIndexHtml. This simplifies SSR setup for applications that don't use the transformIndexHtml API.

Fix raw Rolldown support for Rolldown 1.0.0-beta.44+ (#930)

Rolldown 1.0.0-beta.44+ removed the top-level jsx option in favor of transform.jsx. This plugin now uses the transform.jsx option to support Rolldown 1.0.0-beta.44+.

Commits
  • 3e5a374 release: plugin-react@5.1.0
  • 44cbed4 fix(react): compat with newer rolldown (#930)
  • c54d3c6 chore(deps): update all non-major dependencies (#926)
  • a2d76d9 fix(deps): update all non-major dependencies (#918)
  • fffb7eb feat(react): expose virtual module to simplify hmr preamble setup on ssr (#890)
  • b79592a fix(deps): update react-related dependencies (#901)
  • 2d239fc fix(deps): update all non-major dependencies (#896)
  • 73be2f0 chore(deps): fix vitest > rolldown-vite dependency (#889)
  • 407795d fix(deps): update all non-major dependencies (#887)
  • 47db473 chore(react): fix ecosystem-ci failure (#888)
  • See full diff in compare view

Updates `vite` from 7.1.11 to 7.1.12
Release notes

Sourced from vite's releases.

v7.1.12

Please refer to CHANGELOG.md for details.

Changelog

Sourced from vite's changelog.

7.1.12 (2025-10-23)

Bug Fixes

  • deps: downgrade commonjs plugin to 28.0.6 to avoid rollup/plugins#1909 (#20990) (56fd722)
Commits

Updates `vitest` from 4.0.5 to 4.0.6
Release notes

Sourced from vitest's releases.

v4.0.6

   🐞 Bug Fixes

    View changes on GitHub
Commits
  • 2e7b2b8 chore: release v4.0.6
  • 31706df fix: reuse the same environment when isolate and fileParallelism are fals...
  • 10a06d8 fix(happy-dom): properly teardown additional keys (#8888)
  • 197caf2 fix(jsdom): pass down Node.js FormData to Request (#8880)
  • ca041f5 fix: improve spying types (#8878)
  • e3b7775 fix(coverage): prevent filtering out virtual files before remapping to source...
  • 7e6c37a fix: do not throw when importing a type from an external package (#8875)
  • 3e19f27 fix: don't merge errors with different diffs for reporting (#8871)
  • See full diff in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 6 +- site/pnpm-lock.yaml | 559 ++++++++++++++++++++++++-------------------- 2 files changed, 306 insertions(+), 259 deletions(-) diff --git a/site/package.json b/site/package.json index 4c6ff500badab..b4b202addea60 100644 --- a/site/package.json +++ b/site/package.json @@ -155,7 +155,7 @@ "@types/ssh2": "1.15.5", "@types/ua-parser-js": "0.7.36", "@types/uuid": "9.0.2", - "@vitejs/plugin-react": "5.0.4", + "@vitejs/plugin-react": "5.1.0", "autoprefixer": "10.4.21", "chromatic": "11.29.0", "dpdm": "3.14.0", @@ -180,9 +180,9 @@ "tailwindcss": "3.4.18", "ts-proto": "1.181.2", "typescript": "5.6.3", - "vite": "7.1.11", + "vite": "7.1.12", "vite-plugin-checker": "0.11.0", - "vitest": "4.0.5" + "vitest": "4.0.6" }, "browserslist": [ "chrome 110", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index e880ff0bd76fc..40abe62d9fd21 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -280,7 +280,7 @@ importers: version: 2.2.4 '@chromatic-com/storybook': specifier: 4.1.0 - version: 4.1.0(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 4.1.0(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@octokit/types': specifier: 12.3.0 version: 12.3.0 @@ -289,16 +289,16 @@ importers: version: 1.50.1 '@storybook/addon-docs': specifier: 9.1.2 - version: 9.1.2(@types/react@19.1.17)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 9.1.2(@types/react@19.1.17)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/addon-links': specifier: 9.1.2 - version: 9.1.2(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 9.1.2(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/addon-themes': specifier: 9.1.2 - version: 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/react-vite': specifier: 9.1.2 - version: 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + version: 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) '@swc/core': specifier: 1.3.38 version: 1.3.38 @@ -375,8 +375,8 @@ importers: specifier: 9.0.2 version: 9.0.2 '@vitejs/plugin-react': - specifier: 5.0.4 - version: 5.0.4(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + specifier: 5.1.0 + version: 5.1.0(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) autoprefixer: specifier: 10.4.21 version: 10.4.21(postcss@8.5.6) @@ -436,10 +436,10 @@ importers: version: 1.17.0 storybook: specifier: 9.1.2 - version: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + version: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) storybook-addon-remix-react-router: specifier: 5.0.0 - version: 5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) tailwindcss: specifier: 3.4.18 version: 3.4.18(yaml@2.7.0) @@ -450,14 +450,14 @@ importers: specifier: 5.6.3 version: 5.6.3 vite: - specifier: 7.1.11 - version: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + specifier: 7.1.12 + version: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) vite-plugin-checker: specifier: 0.11.0 - version: 0.11.0(@biomejs/biome@2.2.4)(eslint@8.52.0)(optionator@0.9.3)(typescript@5.6.3)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + version: 0.11.0(@biomejs/biome@2.2.4)(eslint@8.52.0)(optionator@0.9.3)(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) vitest: - specifier: 4.0.5 - version: 4.0.5(@types/debug@4.1.12)(@types/node@20.17.16)(jiti@1.21.7)(jsdom@27.0.1)(msw@2.4.8(typescript@5.6.3))(yaml@2.7.0) + specifier: 4.0.6 + version: 4.0.6(@types/debug@4.1.12)(@types/node@20.17.16)(jiti@1.21.7)(jsdom@27.0.1)(msw@2.4.8(typescript@5.6.3))(yaml@2.7.0) packages: @@ -485,16 +485,16 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==, tarball: https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==, tarball: https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==, tarball: https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz} engines: {node: '>=6.9.0'} - '@babel/core@7.28.4': - resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==, tarball: https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz} + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==, tarball: https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==, tarball: https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==, tarball: https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -527,6 +527,10 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==, tarball: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, tarball: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, tarball: https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz} engines: {node: '>=6.9.0'} @@ -535,8 +539,8 @@ packages: resolution: {integrity: sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==, tarball: https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==, tarball: https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==, tarball: https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz} engines: {node: '>=6.0.0'} hasBin: true @@ -655,16 +659,16 @@ packages: resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==, tarball: https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==, tarball: https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==, tarball: https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz} engines: {node: '>=6.9.0'} '@babel/types@7.27.1': resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==, tarball: https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==, tarball: https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==, tarball: https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -2191,8 +2195,8 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==, tarball: https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz} - '@rolldown/pluginutils@1.0.0-beta.38': - resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==, tarball: https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz} + '@rolldown/pluginutils@1.0.0-beta.43': + resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==, tarball: https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz} '@rollup/pluginutils@5.0.5': resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==, tarball: https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz} @@ -2559,6 +2563,9 @@ packages: '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==, tarball: https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==, tarball: https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz} + '@types/chroma-js@2.4.0': resolution: {integrity: sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw==, tarball: https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.0.tgz} @@ -2816,8 +2823,8 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==, tarball: https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz} - '@vitejs/plugin-react@5.0.4': - resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==, tarball: https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz} + '@vitejs/plugin-react@5.1.0': + resolution: {integrity: sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==, tarball: https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -2825,8 +2832,8 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==, tarball: https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz} - '@vitest/expect@4.0.5': - resolution: {integrity: sha512-DJctLVlKoddvP/G389oGmKWNG6GD9frm2FPXARziU80Rjo7SIYxQzb2YFzmQ4fVD3Q5utUYY8nUmWrqsuIlIXQ==, tarball: https://registry.npmjs.org/@vitest/expect/-/expect-4.0.5.tgz} + '@vitest/expect@4.0.6': + resolution: {integrity: sha512-5j8UUlBVhOjhj4lR2Nt9sEV8b4WtbcYh8vnfhTNA2Kn5+smtevzjNq+xlBuVhnFGXiyPPNzGrOVvmyHWkS5QGg==, tarball: https://registry.npmjs.org/@vitest/expect/-/expect-4.0.6.tgz} '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==, tarball: https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz} @@ -2839,8 +2846,8 @@ packages: vite: optional: true - '@vitest/mocker@4.0.5': - resolution: {integrity: sha512-iYHIy72LfbK+mL5W8zXROp6oOcJKXWeKcNjcPPsqoa18qIEDrhB6/Z08o0wRajTd6SSSDNw8NCSIHVNOMpz0mw==, tarball: https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.5.tgz} + '@vitest/mocker@4.0.6': + resolution: {integrity: sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA==, tarball: https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.6.tgz} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -2853,26 +2860,26 @@ packages: '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==, tarball: https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz} - '@vitest/pretty-format@4.0.5': - resolution: {integrity: sha512-t1T/sSdsYyNc5AZl0EMeD0jW9cpJe2cODP0R++ZQe1kTkpgrwEfxGFR/yCG4w8ZybizbXRTHU7lE8sTDD/QsGw==, tarball: https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.5.tgz} + '@vitest/pretty-format@4.0.6': + resolution: {integrity: sha512-4vptgNkLIA1W1Nn5X4x8rLJBzPiJwnPc+awKtfBE5hNMVsoAl/JCCPPzNrbf+L4NKgklsis5Yp2gYa+XAS442g==, tarball: https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.6.tgz} - '@vitest/runner@4.0.5': - resolution: {integrity: sha512-CQVVe+YEeKSiFBD5gBAmRDQglm4PnMBYzeTmt06t5iWtsUN9StQeeKhYCea/oaqBYilf8sARG6fSctUcEL/UmQ==, tarball: https://registry.npmjs.org/@vitest/runner/-/runner-4.0.5.tgz} + '@vitest/runner@4.0.6': + resolution: {integrity: sha512-trPk5qpd7Jj+AiLZbV/e+KiiaGXZ8ECsRxtnPnCrJr9OW2mLB72Cb824IXgxVz/mVU3Aj4VebY+tDTPn++j1Og==, tarball: https://registry.npmjs.org/@vitest/runner/-/runner-4.0.6.tgz} - '@vitest/snapshot@4.0.5': - resolution: {integrity: sha512-jfmSAeR6xYNEvcD+/RxFGA1bzpqHtkVhgxo2cxXia+Q3xX7m6GpZij07rz+WyQcA/xEGn4eIS1OItkMyWsGBmQ==, tarball: https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.5.tgz} + '@vitest/snapshot@4.0.6': + resolution: {integrity: sha512-PaYLt7n2YzuvxhulDDu6c9EosiRuIE+FI2ECKs6yvHyhoga+2TBWI8dwBjs+IeuQaMtZTfioa9tj3uZb7nev1g==, tarball: https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.6.tgz} '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==, tarball: https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz} - '@vitest/spy@4.0.5': - resolution: {integrity: sha512-TUmVQpAQign7r8+EnZsgTF3vY9BdGofTUge1rGNbnHn2IN3FChiQoT9lrPz7A7AVUZJU2LAZXl4v66HhsNMhoA==, tarball: https://registry.npmjs.org/@vitest/spy/-/spy-4.0.5.tgz} + '@vitest/spy@4.0.6': + resolution: {integrity: sha512-g9jTUYPV1LtRPRCQfhbMintW7BTQz1n6WXYQYRQ25qkyffA4bjVXjkROokZnv7t07OqfaFKw1lPzqKGk1hmNuQ==, tarball: https://registry.npmjs.org/@vitest/spy/-/spy-4.0.6.tgz} '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz} - '@vitest/utils@4.0.5': - resolution: {integrity: sha512-V5RndUgCB5/AfNvK9zxGCrRs99IrPYtMTIdUzJMMFs9nrmE5JXExIEfjVtUteyTRiLfCm+dCRMHf/Uu7Mm8/dg==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-4.0.5.tgz} + '@vitest/utils@4.0.6': + resolution: {integrity: sha512-bG43VS3iYKrMIZXBo+y8Pti0O7uNju3KvNn6DrQWhQQKcLavMB+0NZfO1/QBAEbq0MaQ3QjNsnnXlGQvsh0Z6A==, tarball: https://registry.npmjs.org/@vitest/utils/-/utils-4.0.6.tgz} '@xterm/addon-canvas@0.7.0': resolution: {integrity: sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw==, tarball: https://registry.npmjs.org/@xterm/addon-canvas/-/addon-canvas-0.7.0.tgz} @@ -3094,6 +3101,10 @@ packages: resolution: {integrity: sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==, tarball: https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz} hasBin: true + baseline-browser-mapping@2.8.22: + resolution: {integrity: sha512-/tk9kky/d8T8CTXIQYASLyhAxR5VwL3zct1oAoVTaOUHwrmsGnfbRwNdEq+vOl2BN8i3PcDdP0o4Q+jjKQoFbQ==, tarball: https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.22.tgz} + hasBin: true + bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==, tarball: https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz} @@ -3127,6 +3138,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==, tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==, tarball: https://registry.npmjs.org/bser/-/bser-2.1.1.tgz} @@ -3179,6 +3195,9 @@ packages: caniuse-lite@1.0.30001746: resolution: {integrity: sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz} + caniuse-lite@1.0.30001752: + resolution: {integrity: sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz} + case-anything@2.1.13: resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==, tarball: https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz} engines: {node: '>=12.13'} @@ -3498,15 +3517,6 @@ packages: supports-color: optional: true - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==, tarball: https://registry.npmjs.org/debug/-/debug-4.4.1.tgz} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, tarball: https://registry.npmjs.org/debug/-/debug-4.4.3.tgz} engines: {node: '>=6.0'} @@ -3656,6 +3666,9 @@ packages: electron-to-chromium@1.5.228: resolution: {integrity: sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz} + electron-to-chromium@1.5.244: + resolution: {integrity: sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz} + emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==, tarball: https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz} engines: {node: '>=12'} @@ -5012,6 +5025,9 @@ packages: node-releases@2.0.21: resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==, tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==, tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, tarball: https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz} engines: {node: '>=0.10.0'} @@ -5423,8 +5439,8 @@ packages: '@types/react': '>=18' react: '>=18' - react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==, tarball: https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz} + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==, tarball: https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz} engines: {node: '>=0.10.0'} react-remove-scroll-bar@2.3.8: @@ -6172,6 +6188,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, tarball: https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz} @@ -6303,8 +6325,8 @@ packages: vue-tsc: optional: true - vite@7.1.11: - resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==, tarball: https://registry.npmjs.org/vite/-/vite-7.1.11.tgz} + vite@7.1.12: + resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==, tarball: https://registry.npmjs.org/vite/-/vite-7.1.12.tgz} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -6343,18 +6365,18 @@ packages: yaml: optional: true - vitest@4.0.5: - resolution: {integrity: sha512-4H+J28MI5oeYgGg3h5BFSkQ1g/2GKK1IR8oorH3a6EQQbb7CwjbnyBjH4PGxw9/6vpwAPNzaeUMp4Js4WJmdXQ==, tarball: https://registry.npmjs.org/vitest/-/vitest-4.0.5.tgz} + vitest@4.0.6: + resolution: {integrity: sha512-gR7INfiVRwnEOkCk47faros/9McCZMp5LM+OMNWGLaDBSvJxIzwjgNFufkuePBNaesGRnLmNfW+ddbUJRZn0nQ==, tarball: https://registry.npmjs.org/vitest/-/vitest-4.0.6.tgz} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/debug': ^4.1.12 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.5 - '@vitest/browser-preview': 4.0.5 - '@vitest/browser-webdriverio': 4.0.5 - '@vitest/ui': 4.0.5 + '@vitest/browser-playwright': 4.0.6 + '@vitest/browser-preview': 4.0.6 + '@vitest/browser-webdriverio': 4.0.6 + '@vitest/ui': 4.0.6 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -6612,19 +6634,19 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.4': {} + '@babel/compat-data@7.28.5': {} - '@babel/core@7.28.4': + '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helpers': 7.26.10 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -6634,19 +6656,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.3': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.3 + browserslist: 4.27.0 lru-cache: 5.1.1 semver: 7.7.2 @@ -6654,17 +6676,17 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -6674,110 +6696,112 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helpers@7.26.10': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 - '@babel/parser@7.28.4': + '@babel/parser@7.28.5': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.4)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.4)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.4)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.28.4)': + '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.4)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.28.4)': + '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.4)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.4)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.4)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.4)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.28.4)': + '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 '@babel/runtime@7.26.10': @@ -6787,14 +6811,14 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@babel/traverse@7.27.1': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/parser': 7.28.4 + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 '@babel/types': 7.27.1 debug: 4.4.3 @@ -6802,14 +6826,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/traverse@7.28.4': + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -6819,10 +6843,10 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/types@7.28.4': + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@bcoe/v8-coverage@0.2.3': {} @@ -6874,13 +6898,13 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 - '@chromatic-com/storybook@4.1.0(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@chromatic-com/storybook@4.1.0(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: '@neoconfetti/react': 1.0.0 chromatic: 12.2.0 filesize: 10.1.2 jsonfile: 6.1.0 - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) strip-ansi: 7.1.0 transitivePeerDependencies: - '@chromatic-com/cypress' @@ -7461,7 +7485,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 @@ -7497,12 +7521,12 @@ snapshots: '@types/yargs': 17.0.33 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.6.3)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.1(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: glob: 10.4.5 magic-string: 0.30.17 react-docgen-typescript: 2.2.2(typescript@5.6.3) - vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) optionalDependencies: typescript: 5.6.3 @@ -8331,7 +8355,7 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@rolldown/pluginutils@1.0.0-beta.38': {} + '@rolldown/pluginutils@1.0.0-beta.43': {} '@rollup/pluginutils@5.0.5(rollup@4.52.5)': dependencies: @@ -8419,41 +8443,41 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@storybook/addon-docs@9.1.2(@types/react@19.1.17)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/addon-docs@9.1.2(@types/react@19.1.17)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: '@mdx-js/react': 3.0.1(@types/react@19.1.17)(react@19.1.1) - '@storybook/csf-plugin': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + '@storybook/csf-plugin': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/icons': 1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-links@9.1.2(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/addon-links@9.1.2(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: react: 19.1.1 - '@storybook/addon-themes@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/addon-themes@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) ts-dedent: 2.2.0 - '@storybook/builder-vite@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + '@storybook/builder-vite@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: - '@storybook/csf-plugin': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + '@storybook/csf-plugin': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) ts-dedent: 2.2.0 - vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) - '@storybook/csf-plugin@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/csf-plugin@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) unplugin: 1.5.0 '@storybook/global@5.0.0': {} @@ -8463,39 +8487,39 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@storybook/react-dom-shim@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/react-dom-shim@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) - '@storybook/react-vite@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + '@storybook/react-vite@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.6.3)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) '@rollup/pluginutils': 5.0.5(rollup@4.52.5) - '@storybook/builder-vite': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) - '@storybook/react': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3) + '@storybook/builder-vite': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + '@storybook/react': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3) find-up: 7.0.0 magic-string: 0.30.17 react: 19.1.1 react-docgen: 8.0.0 react-dom: 19.1.1(react@19.1.1) resolve: 1.22.10 - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) tsconfig-paths: 4.2.0 - vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) transitivePeerDependencies: - rollup - supports-color - typescript - '@storybook/react@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)': + '@storybook/react@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: typescript: 5.6.3 @@ -8644,20 +8668,20 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__traverse@7.20.6': dependencies: @@ -8665,7 +8689,7 @@ snapshots: '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@types/body-parser@1.19.2': dependencies: @@ -8676,6 +8700,11 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/chroma-js@2.4.0': {} '@types/color-convert@2.0.4': @@ -8936,15 +8965,15 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@5.0.4(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + '@vitejs/plugin-react@5.1.0(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: - '@babel/core': 7.28.4 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) - '@rolldown/pluginutils': 1.0.0-beta.38 + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.43 '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + react-refresh: 0.18.0 + vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -8956,49 +8985,49 @@ snapshots: chai: 5.2.1 tinyrainbow: 2.0.0 - '@vitest/expect@4.0.5': + '@vitest/expect@4.0.6': dependencies: '@standard-schema/spec': 1.0.0 - '@types/chai': 5.2.2 - '@vitest/spy': 4.0.5 - '@vitest/utils': 4.0.5 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.6 + '@vitest/utils': 4.0.6 chai: 6.2.0 tinyrainbow: 3.0.3 - '@vitest/mocker@3.2.4(msw@2.4.8(typescript@5.6.3))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + '@vitest/mocker@3.2.4(msw@2.4.8(typescript@5.6.3))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.17 + magic-string: 0.30.21 optionalDependencies: msw: 2.4.8(typescript@5.6.3) - vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) - '@vitest/mocker@4.0.5(msw@2.4.8(typescript@5.6.3))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + '@vitest/mocker@4.0.6(msw@2.4.8(typescript@5.6.3))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: - '@vitest/spy': 4.0.5 + '@vitest/spy': 4.0.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.4.8(typescript@5.6.3) - vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/pretty-format@4.0.5': + '@vitest/pretty-format@4.0.6': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.5': + '@vitest/runner@4.0.6': dependencies: - '@vitest/utils': 4.0.5 + '@vitest/utils': 4.0.6 pathe: 2.0.3 - '@vitest/snapshot@4.0.5': + '@vitest/snapshot@4.0.6': dependencies: - '@vitest/pretty-format': 4.0.5 + '@vitest/pretty-format': 4.0.6 magic-string: 0.30.21 pathe: 2.0.3 @@ -9006,7 +9035,7 @@ snapshots: dependencies: tinyspy: 4.0.3 - '@vitest/spy@4.0.5': {} + '@vitest/spy@4.0.6': {} '@vitest/utils@3.2.4': dependencies: @@ -9014,9 +9043,9 @@ snapshots: loupe: 3.2.0 tinyrainbow: 2.0.0 - '@vitest/utils@4.0.5': + '@vitest/utils@4.0.6': dependencies: - '@vitest/pretty-format': 4.0.5 + '@vitest/pretty-format': 4.0.6 tinyrainbow: 3.0.3 '@xterm/addon-canvas@0.7.0(@xterm/xterm@5.5.0)': @@ -9182,13 +9211,13 @@ snapshots: transitivePeerDependencies: - debug - babel-jest@29.7.0(@babel/core@7.28.4): + babel-jest@29.7.0(@babel/core@7.28.5): dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.28.4) + babel-preset-jest: 29.6.3(@babel/core@7.28.5) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -9208,7 +9237,7 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 @@ -9218,30 +9247,30 @@ snapshots: cosmiconfig: 7.1.0 resolve: 1.22.10 - babel-preset-current-node-syntax@1.1.0(@babel/core@7.28.4): - dependencies: - '@babel/core': 7.28.4 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.4) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.4) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.4) - '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.28.4) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.4) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.4) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.4) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.4) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.4) - - babel-preset-jest@29.6.3(@babel/core@7.28.4): - dependencies: - '@babel/core': 7.28.4 + babel-preset-current-node-syntax@1.1.0(@babel/core@7.28.5): + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.5) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.5) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.5) + '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.28.5) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.5) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.5) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.5) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5) + + babel-preset-jest@29.6.3(@babel/core@7.28.5): + dependencies: + '@babel/core': 7.28.5 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.4) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.5) bail@2.0.2: {} @@ -9251,6 +9280,8 @@ snapshots: baseline-browser-mapping@2.8.10: {} + baseline-browser-mapping@2.8.22: {} + bcrypt-pbkdf@1.0.2: dependencies: tweetnacl: 0.14.5 @@ -9305,6 +9336,14 @@ snapshots: node-releases: 2.0.21 update-browserslist-db: 1.1.3(browserslist@4.26.3) + browserslist@4.27.0: + dependencies: + baseline-browser-mapping: 2.8.22 + caniuse-lite: 1.0.30001752 + electron-to-chromium: 1.5.244 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.27.0) + bser@2.1.1: dependencies: node-int64: 0.4.0 @@ -9356,6 +9395,8 @@ snapshots: caniuse-lite@1.0.30001746: {} + caniuse-lite@1.0.30001752: {} + case-anything@2.1.13: {} ccount@2.0.1: {} @@ -9629,10 +9670,6 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -9775,6 +9812,8 @@ snapshots: electron-to-chromium@1.5.228: {} + electron-to-chromium@1.5.244: {} + emittery@0.13.1: {} emoji-mart@5.6.0: {} @@ -9828,7 +9867,7 @@ snapshots: esbuild-register@3.6.0(esbuild@0.25.3): dependencies: - debug: 4.4.1 + debug: 4.4.3 esbuild: 0.25.3 transitivePeerDependencies: - supports-color @@ -10594,8 +10633,8 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.7.2 @@ -10604,8 +10643,8 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.7.2 @@ -10695,10 +10734,10 @@ snapshots: jest-config@29.7.0(@types/node@20.17.16)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.3.38)(@types/node@20.17.16)(typescript@5.6.3)): dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.28.4) + babel-jest: 29.7.0(@babel/core@7.28.5) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -10931,15 +10970,15 @@ snapshots: jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.28.4 - '@babel/generator': 7.28.3 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.28.4) - '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.28.4) - '@babel/types': 7.28.4 + '@babel/core': 7.28.5 + '@babel/generator': 7.28.5 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.28.5) + '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.28.5) + '@babel/types': 7.28.5 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.4) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.5) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -11698,6 +11737,8 @@ snapshots: node-releases@2.0.21: {} + node-releases@2.0.27: {} + normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -12085,7 +12126,7 @@ snapshots: react-docgen@8.0.0: dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/traverse': 7.27.1 '@babel/types': 7.27.1 '@types/babel__core': 7.20.5 @@ -12140,7 +12181,7 @@ snapshots: transitivePeerDependencies: - supports-color - react-refresh@0.17.0: {} + react-refresh@0.18.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.1.17)(react@19.1.1): dependencies: @@ -12579,24 +12620,24 @@ snapshots: dependencies: internal-slot: 1.0.6 - storybook-addon-remix-react-router@5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))): + storybook-addon-remix-react-router@5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))): dependencies: '@mjackson/form-data-parser': 0.4.0 compare-versions: 6.1.0 react-inspector: 6.0.2(react@19.1.1) react-router: 7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)): + storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)): dependencies: '@storybook/global': 5.0.0 '@testing-library/jest-dom': 6.9.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.4.8(typescript@5.6.3))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + '@vitest/mocker': 3.2.4(msw@2.4.8(typescript@5.6.3))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) '@vitest/spy': 3.2.4 better-opn: 3.0.2 esbuild: 0.25.3 @@ -12969,6 +13010,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.1.4(browserslist@4.27.0): + dependencies: + browserslist: 4.27.0 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -13065,7 +13112,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-plugin-checker@0.11.0(@biomejs/biome@2.2.4)(eslint@8.52.0)(optionator@0.9.3)(typescript@5.6.3)(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)): + vite-plugin-checker@0.11.0(@biomejs/biome@2.2.4)(eslint@8.52.0)(optionator@0.9.3)(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)): dependencies: '@babel/code-frame': 7.27.1 chokidar: 4.0.3 @@ -13074,7 +13121,7 @@ snapshots: picomatch: 4.0.3 tiny-invariant: 1.3.3 tinyglobby: 0.2.15 - vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) vscode-uri: 3.1.0 optionalDependencies: '@biomejs/biome': 2.2.4 @@ -13082,7 +13129,7 @@ snapshots: optionator: 0.9.3 typescript: 5.6.3 - vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0): + vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -13096,15 +13143,15 @@ snapshots: jiti: 1.21.7 yaml: 2.7.0 - vitest@4.0.5(@types/debug@4.1.12)(@types/node@20.17.16)(jiti@1.21.7)(jsdom@27.0.1)(msw@2.4.8(typescript@5.6.3))(yaml@2.7.0): + vitest@4.0.6(@types/debug@4.1.12)(@types/node@20.17.16)(jiti@1.21.7)(jsdom@27.0.1)(msw@2.4.8(typescript@5.6.3))(yaml@2.7.0): dependencies: - '@vitest/expect': 4.0.5 - '@vitest/mocker': 4.0.5(msw@2.4.8(typescript@5.6.3))(vite@7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) - '@vitest/pretty-format': 4.0.5 - '@vitest/runner': 4.0.5 - '@vitest/snapshot': 4.0.5 - '@vitest/spy': 4.0.5 - '@vitest/utils': 4.0.5 + '@vitest/expect': 4.0.6 + '@vitest/mocker': 4.0.6(msw@2.4.8(typescript@5.6.3))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + '@vitest/pretty-format': 4.0.6 + '@vitest/runner': 4.0.6 + '@vitest/snapshot': 4.0.6 + '@vitest/spy': 4.0.6 + '@vitest/utils': 4.0.6 debug: 4.4.3 es-module-lexer: 1.7.0 expect-type: 1.2.2 @@ -13116,7 +13163,7 @@ snapshots: tinyexec: 0.3.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.1.11(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) + vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 From ea533aa5229d8c4051c7757ca184d96543dd4d3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:21:47 +0000 Subject: [PATCH 053/255] chore: bump @octokit/types from 12.3.0 to 12.6.0 in /site (#20619) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@octokit/types](https://github.com/octokit/types.ts) from 12.3.0 to 12.6.0.
Release notes

Sourced from @​octokit/types's releases.

v12.6.0

12.6.0 (2024-02-22)

Features

v12.5.0

12.5.0 (2024-02-15)

Features

v12.4.0

12.4.0 (2023-12-04)

Features

  • permissions.organization_custom_properties (#598) (ff98468)
Commits
  • b2d7de9 feat: many new endpoints (#618)
  • bc28bdc feat: add x-accepted-github-permissions (#609)
  • fa1e25b chore(deps): update dependency npm-run-all2 to v6
  • 349bf94 chore(deps): replace dependency npm-run-all with npm-run-all2 ^5.0.0
  • 47eb4d0 ci(action): update peter-evans/create-or-update-comment action to v4
  • 7e554cd chore(deps): update dependency semantic-release to v23
  • 40bc39f ci(action): update github/codeql-action action to v3
  • dbc66ec 🚧 Workflows have changed (#600)
  • ff98468 feat: permissions.organization_custom_properties (#598)
  • 1397259 build(deps): lock file maintenance
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@octokit/types&package-manager=npm_and_yarn&previous-version=12.3.0&new-version=12.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/site/package.json b/site/package.json index b4b202addea60..4f1efe681aa3b 100644 --- a/site/package.json +++ b/site/package.json @@ -124,7 +124,7 @@ "devDependencies": { "@biomejs/biome": "2.2.4", "@chromatic-com/storybook": "4.1.0", - "@octokit/types": "12.3.0", + "@octokit/types": "12.6.0", "@playwright/test": "1.50.1", "@storybook/addon-docs": "9.1.2", "@storybook/addon-links": "9.1.2", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 40abe62d9fd21..329c66deab971 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -282,8 +282,8 @@ importers: specifier: 4.1.0 version: 4.1.0(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@octokit/types': - specifier: 12.3.0 - version: 12.3.0 + specifier: 12.6.0 + version: 12.6.0 '@playwright/test': specifier: 1.50.1 version: 1.50.1 @@ -1525,11 +1525,11 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, tarball: https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz} engines: {node: '>= 8'} - '@octokit/openapi-types@19.0.2': - resolution: {integrity: sha512-8li32fUDUeml/ACRp/njCWTsk5t17cfTM1jp9n08pBrqs5cDFJubtjsSnuz56r5Tad6jdEPJld7LxNp9dNcyjQ==, tarball: https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.2.tgz} + '@octokit/openapi-types@20.0.0': + resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==, tarball: https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz} - '@octokit/types@12.3.0': - resolution: {integrity: sha512-nJ8X2HRr234q3w/FcovDlA+ttUU4m1eJAourvfUUtwAWeqL8AsyRqfnLvVnYn3NFbUnsmzQCzLNdFerPwdmcDQ==, tarball: https://registry.npmjs.org/@octokit/types/-/types-12.3.0.tgz} + '@octokit/types@12.6.0': + resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==, tarball: https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz} '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==, tarball: https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz} @@ -7724,11 +7724,11 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@octokit/openapi-types@19.0.2': {} + '@octokit/openapi-types@20.0.0': {} - '@octokit/types@12.3.0': + '@octokit/types@12.6.0': dependencies: - '@octokit/openapi-types': 19.0.2 + '@octokit/openapi-types': 20.0.0 '@open-draft/deferred-promise@2.2.0': {} From cf746f3a87086ac9c334d8acc0143f2d1ec3da8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:22:26 +0000 Subject: [PATCH 054/255] chore: bump @fontsource/jetbrains-mono from 5.2.5 to 5.2.8 in /site (#20621) Bumps [@fontsource/jetbrains-mono](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/jetbrains-mono) from 5.2.5 to 5.2.8.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@fontsource/jetbrains-mono&package-manager=npm_and_yarn&previous-version=5.2.5&new-version=5.2.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/package.json b/site/package.json index 4f1efe681aa3b..4e742b3d821e0 100644 --- a/site/package.json +++ b/site/package.json @@ -44,7 +44,7 @@ "@fontsource-variable/inter": "5.1.1", "@fontsource/fira-code": "5.2.7", "@fontsource/ibm-plex-mono": "5.2.7", - "@fontsource/jetbrains-mono": "5.2.5", + "@fontsource/jetbrains-mono": "5.2.8", "@fontsource/source-code-pro": "5.2.5", "@monaco-editor/react": "4.7.0", "@mui/material": "5.18.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 329c66deab971..cae7b29c0422d 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -47,8 +47,8 @@ importers: specifier: 5.2.7 version: 5.2.7 '@fontsource/jetbrains-mono': - specifier: 5.2.5 - version: 5.2.5 + specifier: 5.2.8 + version: 5.2.8 '@fontsource/source-code-pro': specifier: 5.2.5 version: 5.2.5 @@ -1201,8 +1201,8 @@ packages: '@fontsource/ibm-plex-mono@5.2.7': resolution: {integrity: sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==, tarball: https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.2.7.tgz} - '@fontsource/jetbrains-mono@5.2.5': - resolution: {integrity: sha512-TPZ9b/uq38RMdrlZZkl0RwN8Ju9JxuqMETrw76pUQFbGtE1QbwQaNsLlnUrACNNBNbd0NZRXiJJSkC8ajPgbew==, tarball: https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.5.tgz} + '@fontsource/jetbrains-mono@5.2.8': + resolution: {integrity: sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==, tarball: https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz} '@fontsource/source-code-pro@5.2.5': resolution: {integrity: sha512-1k7b9IdhVSdK/rJ8CkqqGFZ01C3NaXNynPZqKaTetODog/GPJiMYd6E8z+LTwSUTIX8dm2QZORDC+Uh91cjXSg==, tarball: https://registry.npmjs.org/@fontsource/source-code-pro/-/source-code-pro-5.2.5.tgz} @@ -7256,7 +7256,7 @@ snapshots: '@fontsource/ibm-plex-mono@5.2.7': {} - '@fontsource/jetbrains-mono@5.2.5': {} + '@fontsource/jetbrains-mono@5.2.8': {} '@fontsource/source-code-pro@5.2.5': {} From 37432aefa6fbd7fce71eb115784b7695c3c8c235 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:23:12 +0000 Subject: [PATCH 055/255] chore: bump rxjs from 7.8.1 to 7.8.2 in /site (#20622) Bumps [rxjs](https://github.com/reactivex/rxjs) from 7.8.1 to 7.8.2.
Changelog

Sourced from rxjs's changelog.

7.8.2 (2025-02-22)

Bug Fixes

  • animationFrameScheduler: some tasks are never flushed and sometimes it breaks completely (#7444) (8bbfa4e)
  • mergeWith: works correctly with an Array (#7281) (27855c6)
  • subscriber: strict type signature for next method (#7172) (0e2ef5e)
Commits
  • e5351d0 chore(publish): 7.8.2
  • 8bbfa4e fix(animationFrameScheduler): some tasks are never flushed and sometimes it b...
  • 4a2d0d2 docs(rxjs.dex): replace polyfill.io with a Cloudflare equivalent (#7487)
  • 2fb0740 chore: 7.x remove global npm install and ignore latest TS (#7398)
  • d69d890 docs: fix missing overloads in docs when overload count is less than 3 (#7367...
  • 27855c6 fix(mergeWith): works correctly with an Array (#7281)
  • 9db6563 docs: provide URL for the V8 docs app (#7244)
  • 5c3fb33 docs: add MonoTypeOperatorFunction documentation (#7284)
  • 0e2ef5e fix(subscriber): strict type signature for next method (#7172)
  • b6d00c1 docs: improve glossary and semantics page (#7267)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rxjs&package-manager=npm_and_yarn&previous-version=7.8.1&new-version=7.8.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/site/package.json b/site/package.json index 4e742b3d821e0..7e28164361657 100644 --- a/site/package.json +++ b/site/package.json @@ -173,7 +173,7 @@ "postcss": "8.5.6", "protobufjs": "7.4.0", "rollup-plugin-visualizer": "5.14.0", - "rxjs": "7.8.1", + "rxjs": "7.8.2", "ssh2": "1.17.0", "storybook": "9.1.2", "storybook-addon-remix-react-router": "5.0.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index cae7b29c0422d..3cda5fa27e6db 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -429,8 +429,8 @@ importers: specifier: 5.14.0 version: 5.14.0(rollup@4.52.5) rxjs: - specifier: 7.8.1 - version: 7.8.1 + specifier: 7.8.2 + version: 7.8.2 ssh2: specifier: 1.17.0 version: 1.17.0 @@ -5664,8 +5664,8 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, tarball: https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==, tarball: https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==, tarball: https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz} safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, tarball: https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz} @@ -12461,9 +12461,9 @@ snapshots: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: + rxjs@7.8.2: dependencies: - tslib: 2.6.2 + tslib: 2.8.1 safe-buffer@5.1.2: {} From 2734123ac2a91b2211abe2fcd89024799e319896 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:23:39 +0000 Subject: [PATCH 056/255] chore: bump axios from 1.12.0 to 1.13.1 in /site (#20623) Bumps [axios](https://github.com/axios/axios) from 1.12.0 to 1.13.1.
Release notes

Sourced from axios's releases.

Release v1.13.1

Release notes:

Bug Fixes

  • http: fixed a regression that caused the data stream to be interrupted for responses with non-OK HTTP statuses; (#7193) (bcd5581)

Contributors to this release

Release v1.13.0

Release notes:

Bug Fixes

  • fetch: prevent TypeError when config.env is undefined (#7155) (015faec)
  • resolve issue #7131 (added spacing in mergeConfig.js) (#7133) (9b9ec98)

Features

Contributors to this release

Release v1.12.2

Release notes:

Bug Fixes

  • fetch: use current global fetch instead of cached one when env fetch is not specified to keep MSW support; (#7030) (cf78825)

Contributors to this release

... (truncated)

Changelog

Sourced from axios's changelog.

1.13.1 (2025-10-28)

Bug Fixes

  • http: fixed a regression that caused the data stream to be interrupted for responses with non-OK HTTP statuses; (#7193) (bcd5581)

Contributors to this release

1.13.0 (2025-10-27)

Bug Fixes

  • fetch: prevent TypeError when config.env is undefined (#7155) (015faec)
  • resolve issue #7131 (added spacing in mergeConfig.js) (#7133) (9b9ec98)

Features

Contributors to this release

1.12.2 (2025-09-14)

Bug Fixes

... (truncated)

Commits
  • 1ef8e72 chore(release): v1.13.1 (#7194)
  • bcd5581 fix(http): fixed a regression that caused the data stream to be interrupted f...
  • c9b3371 chore: enhance styling and responsiveness in client.html (#7173)
  • 9ead04d [Release] v1.13.0 (#7189)
  • d000fbf fix(http2): fix possible race condition when handling http2 stream on almost ...
  • 08db960 docs: added example for improved network error handling (with Wrapper/Middlew...
  • 46e1981 refactor: form data handling in index.html (#7170)
  • 889f8ef docs: fix mismatched return type (#7172)
  • 7b197ef fix: sandbox ui updated (#7175)
  • 6dff629 chore: fix typos in examples (#7166)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=axios&package-manager=npm_and_yarn&previous-version=1.12.0&new-version=1.13.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 53 ++++++++++++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/site/package.json b/site/package.json index 7e28164361657..359a4886a3d9c 100644 --- a/site/package.json +++ b/site/package.json @@ -74,7 +74,7 @@ "@xterm/addon-webgl": "0.18.0", "@xterm/xterm": "5.5.0", "ansi-to-html": "0.7.2", - "axios": "1.12.0", + "axios": "1.13.1", "chroma-js": "2.6.0", "class-variance-authority": "0.7.1", "clsx": "2.1.1", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 3cda5fa27e6db..2bf3bde4a65fc 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -137,8 +137,8 @@ importers: specifier: 0.7.2 version: 0.7.2 axios: - specifier: 1.12.0 - version: 1.12.0 + specifier: 1.13.1 + version: 1.13.1 chroma-js: specifier: 2.6.0 version: 2.6.0 @@ -3056,8 +3056,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, tarball: https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz} engines: {node: '>= 0.4'} - axios@1.12.0: - resolution: {integrity: sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==, tarball: https://registry.npmjs.org/axios/-/axios-1.12.0.tgz} + axios@1.13.1: + resolution: {integrity: sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==, tarball: https://registry.npmjs.org/axios/-/axios-1.13.1.tgz} babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==, tarball: https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz} @@ -4006,6 +4006,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, tarball: https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz} engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, tarball: https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz} + engines: {node: '>= 0.4'} + get-intrinsic@1.3.1: resolution: {integrity: sha512-fk1ZVEeOX9hVZ6QzoBNEC55+Ucqg4sTVwrVuigZhuRPESVFpMyXnd3sbXvPOwp7Y9riVyANiqhEuRF0G1aVSeQ==, tarball: https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.1.tgz} engines: {node: '>= 0.4'} @@ -9203,7 +9207,7 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 - axios@1.12.0: + axios@1.13.1: dependencies: follow-redirects: 1.15.11 form-data: 4.0.4 @@ -9370,20 +9374,20 @@ snapshots: es-define-property: 1.0.1 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 set-function-length: 1.2.2 call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 set-function-length: 1.2.2 call-bound@1.0.3: dependencies: call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 callsites@3.1.0: {} @@ -9724,7 +9728,7 @@ snapshots: define-data-property@1.1.1: dependencies: - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.1 @@ -9843,7 +9847,7 @@ snapshots: es-get-iterator@1.1.3: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 has-symbols: 1.1.0 is-arguments: 1.2.0 is-map: 2.0.2 @@ -9861,7 +9865,7 @@ snapshots: es-set-tostringtag@2.1.0: dependencies: es-errors: 1.3.0 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -10260,6 +10264,19 @@ snapshots: get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-intrinsic@1.3.1: dependencies: async-function: 1.0.0 @@ -10335,7 +10352,7 @@ snapshots: has-property-descriptors@1.0.1: dependencies: - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 has-property-descriptors@1.0.2: dependencies: @@ -10488,7 +10505,7 @@ snapshots: internal-slot@1.0.6: dependencies: - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 hasown: 2.0.2 side-channel: 1.1.0 @@ -10518,7 +10535,7 @@ snapshots: is-array-buffer@3.0.2: dependencies: call-bind: 1.0.7 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 is-typed-array: 1.1.15 is-arrayish@0.2.1: {} @@ -10617,7 +10634,7 @@ snapshots: is-weakset@2.0.2: dependencies: call-bind: 1.0.8 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 is-wsl@2.2.0: dependencies: @@ -12513,7 +12530,7 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 @@ -12544,14 +12561,14 @@ snapshots: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 object-inspect: 1.13.3 side-channel-weakmap@1.0.2: dependencies: call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.3.1 + get-intrinsic: 1.3.0 object-inspect: 1.13.3 side-channel-map: 1.0.1 From 0eb8e904a1dedc9bc0a74de1656aa7387ab1eeb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:24:03 +0000 Subject: [PATCH 057/255] chore: bump lucide-react from 0.545.0 to 0.552.0 in /site (#20625) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.545.0 to 0.552.0.
Release notes

Sourced from lucide-react's releases.

Version 0.552.0

What's Changed

Full Changelog: https://github.com/lucide-icons/lucide/compare/0.551.0...0.552.0

Version 0.551.0

What's Changed

Full Changelog: https://github.com/lucide-icons/lucide/compare/0.550.0...0.551.0

Version 0.550.0

What's Changed

New Contributors

Full Changelog: https://github.com/lucide-icons/lucide/compare/0.549.0...0.550.0

Version 0.549.0

What's Changed

New Contributors

Full Changelog: https://github.com/lucide-icons/lucide/compare/0.548.0...0.549.0

Version 0.548.0

What's Changed

New Contributors

Full Changelog: https://github.com/lucide-icons/lucide/compare/0.547.0...0.548.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=lucide-react&package-manager=npm_and_yarn&previous-version=0.545.0&new-version=0.552.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/package.json b/site/package.json index 359a4886a3d9c..e5e1c00aa4608 100644 --- a/site/package.json +++ b/site/package.json @@ -90,7 +90,7 @@ "humanize-duration": "3.32.2", "jszip": "3.10.1", "lodash": "4.17.21", - "lucide-react": "0.545.0", + "lucide-react": "0.552.0", "monaco-editor": "0.53.0", "pretty-bytes": "6.1.1", "react": "19.1.1", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 2bf3bde4a65fc..12d0fda1ed1bb 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -185,8 +185,8 @@ importers: specifier: 4.17.21 version: 4.17.21 lucide-react: - specifier: 0.545.0 - version: 0.545.0(react@19.1.1) + specifier: 0.552.0 + version: 0.552.0(react@19.1.1) monaco-editor: specifier: 0.53.0 version: 0.53.0 @@ -4739,8 +4739,8 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz} - lucide-react@0.545.0: - resolution: {integrity: sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw==, tarball: https://registry.npmjs.org/lucide-react/-/lucide-react-0.545.0.tgz} + lucide-react@0.552.0: + resolution: {integrity: sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==, tarball: https://registry.npmjs.org/lucide-react/-/lucide-react-0.552.0.tgz} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -11275,7 +11275,7 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@0.545.0(react@19.1.1): + lucide-react@0.552.0(react@19.1.1): dependencies: react: 19.1.1 From db22227f08fcd3aab7cfe09dfbf6944700fb89a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:25:44 +0000 Subject: [PATCH 058/255] chore: bump cronstrue from 2.50.0 to 2.59.0 in /site (#20628) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [cronstrue](https://github.com/bradymholt/cronstrue) from 2.50.0 to 2.59.0.
Release notes

Sourced from cronstrue's releases.

v2.59.0

What's Changed

New Contributors

Full Changelog: https://github.com/bradymholt/cRonstrue/compare/v2.58.0...v2.59.0

v2.58.0

What's Changed

Full Changelog: https://github.com/bradymholt/cRonstrue/compare/v2.57.0...v2.58.0

v2.57.0

What's Changed

New Contributors

Full Changelog: https://github.com/bradymholt/cRonstrue/compare/v2.56.0...v2.57.0

v2.56.0

What's Changed

New Contributors

Full Changelog: https://github.com/bradymholt/cRonstrue/compare/v2.55.0...v2.56.0

v2.55.0

What's Changed

Full Changelog: https://github.com/bradymholt/cRonstrue/compare/v2.54.0...v2.55.0

v2.54.0

What's Changed

New Contributors

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cronstrue&package-manager=npm_and_yarn&previous-version=2.50.0&new-version=2.59.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/package.json b/site/package.json index e5e1c00aa4608..5bfa906a56014 100644 --- a/site/package.json +++ b/site/package.json @@ -81,7 +81,7 @@ "cmdk": "1.0.4", "color-convert": "2.0.1", "cron-parser": "4.9.0", - "cronstrue": "2.50.0", + "cronstrue": "2.59.0", "dayjs": "1.11.18", "emoji-mart": "5.6.0", "file-saver": "2.0.5", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 12d0fda1ed1bb..2fc9f4aa042d4 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -158,8 +158,8 @@ importers: specifier: 4.9.0 version: 4.9.0 cronstrue: - specifier: 2.50.0 - version: 2.50.0 + specifier: 2.59.0 + version: 2.59.0 dayjs: specifier: 1.11.18 version: 1.11.18 @@ -3410,8 +3410,8 @@ packages: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==, tarball: https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz} engines: {node: '>=12.0.0'} - cronstrue@2.50.0: - resolution: {integrity: sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg==, tarball: https://registry.npmjs.org/cronstrue/-/cronstrue-2.50.0.tgz} + cronstrue@2.59.0: + resolution: {integrity: sha512-YKGmAy84hKH+hHIIER07VCAHf9u0Ldelx1uU6EBxsRPDXIA1m5fsKmJfyC3xBhw6cVC/1i83VdbL4PvepTrt8A==, tarball: https://registry.npmjs.org/cronstrue/-/cronstrue-2.59.0.tgz} hasBin: true cross-spawn@7.0.6: @@ -9580,7 +9580,7 @@ snapshots: dependencies: luxon: 3.3.0 - cronstrue@2.50.0: {} + cronstrue@2.59.0: {} cross-spawn@7.0.6: dependencies: From dc9166b4cdff0f8135962c9bb40e387d4948c8c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:26:26 +0000 Subject: [PATCH 059/255] chore: bump recharts from 2.15.0 to 2.15.4 in /site (#20629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [recharts](https://github.com/recharts/recharts) from 2.15.0 to 2.15.4.
Release notes

Sourced from recharts's releases.

v2.15.4

What's Changed

Last 2.x patch - releasing since the @babel/runtime vulnerability is showing up in some security scans. Hoping to release 3.0 on 6/22 🚀

Fix

Security

  • resolve @​babel/runtime ReDoS vulnerability (SNYK-JS-BABELRUNTIME-10044504) by @​moehaje in recharts/recharts#5969
    • recharts isn't vulnerable to this per-se, but it does show up in security tooling like snyk

New Contributors

Full Changelog: https://github.com/recharts/recharts/compare/v2.15.3...v2.15.4

v2.15.3

Last patch release before 3.0 🚀

What's Changed

Fix

Full Changelog: https://github.com/recharts/recharts/compare/v2.15.2...v2.15.3

v2.15.2

What's Changed

Few bugfixes and bug fix backports for 2.x

Fix

New Contributors

... (truncated)

Commits
  • 7baebfe 2.15.4
  • a059da3 fix: resolve @​babel/runtime ReDoS vulnerability (SNYK-JS-BABELRUNTIME-1004450...
  • b835f0e feat: allow minPointSize function to receive null and undefined values (2.x) ...
  • 7921cda fix: combine custom-tick-className and cartesian-axis-tick-value (#5840)
  • 5a3057a Import react with namespace import (#5810)
  • bfd18c2 2.15.3
  • 6d65542 Fix XAxis padding calculation (#5759)
  • 2ce39b9 2.15.2
  • 2783232 Fix activeBar Prop Not Working when tooltip shared is false (#5718)
  • 2b1afa7 Add SVGElements to payload type (#5712)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=recharts&package-manager=npm_and_yarn&previous-version=2.15.0&new-version=2.15.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 42 +++++++++++++++++++++--------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/site/package.json b/site/package.json index 5bfa906a56014..98b1b56bb72f8 100644 --- a/site/package.json +++ b/site/package.json @@ -106,7 +106,7 @@ "react-textarea-autosize": "8.5.9", "react-virtualized-auto-sizer": "1.0.26", "react-window": "1.8.11", - "recharts": "2.15.0", + "recharts": "2.15.4", "remark-gfm": "4.0.1", "resize-observer-polyfill": "1.5.1", "semver": "7.7.2", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 2fc9f4aa042d4..8db76c1c2b45b 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -233,8 +233,8 @@ importers: specifier: 1.8.11 version: 1.8.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) recharts: - specifier: 2.15.0 - version: 2.15.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 2.15.4 + version: 2.15.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) remark-gfm: specifier: 4.0.1 version: 4.0.1 @@ -2581,8 +2581,8 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==, tarball: https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz} - '@types/d3-array@3.2.1': - resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==, tarball: https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==, tarball: https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz} '@types/d3-color@3.1.3': resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==, tarball: https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz} @@ -2593,11 +2593,11 @@ packages: '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==, tarball: https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz} - '@types/d3-path@3.1.0': - resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==, tarball: https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz} + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==, tarball: https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz} - '@types/d3-scale@4.0.8': - resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==, tarball: https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz} + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==, tarball: https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz} '@types/d3-shape@3.1.7': resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==, tarball: https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz} @@ -3846,8 +3846,8 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, tarball: https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz} - fast-equals@5.2.2: - resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==, tarball: https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz} + fast-equals@5.3.2: + resolution: {integrity: sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==, tarball: https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz} engines: {node: '>=6.0.0'} fast-glob@3.3.3: @@ -5563,8 +5563,8 @@ packages: recharts-scale@0.4.5: resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==, tarball: https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz} - recharts@2.15.0: - resolution: {integrity: sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==, tarball: https://registry.npmjs.org/recharts/-/recharts-2.15.0.tgz} + recharts@2.15.4: + resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==, tarball: https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz} engines: {node: '>=14'} peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -8723,7 +8723,7 @@ snapshots: '@types/cookie@0.6.0': {} - '@types/d3-array@3.2.1': {} + '@types/d3-array@3.2.2': {} '@types/d3-color@3.1.3': {} @@ -8733,15 +8733,15 @@ snapshots: dependencies: '@types/d3-color': 3.1.3 - '@types/d3-path@3.1.0': {} + '@types/d3-path@3.1.1': {} - '@types/d3-scale@4.0.8': + '@types/d3-scale@4.0.9': dependencies: '@types/d3-time': 3.0.4 '@types/d3-shape@3.1.7': dependencies: - '@types/d3-path': 3.1.0 + '@types/d3-path': 3.1.1 '@types/d3-time@3.0.4': {} @@ -10104,7 +10104,7 @@ snapshots: fast-deep-equal@3.1.3: optional: true - fast-equals@5.2.2: {} + fast-equals@5.3.2: {} fast-glob@3.3.3: dependencies: @@ -12234,7 +12234,7 @@ snapshots: react-smooth@4.0.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: - fast-equals: 5.2.2 + fast-equals: 5.3.2 prop-types: 15.8.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -12333,7 +12333,7 @@ snapshots: dependencies: decimal.js-light: 2.5.1 - recharts@2.15.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + recharts@2.15.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: clsx: 2.1.1 eventemitter3: 4.0.7 @@ -13114,10 +13114,10 @@ snapshots: victory-vendor@36.9.2: dependencies: - '@types/d3-array': 3.2.1 + '@types/d3-array': 3.2.2 '@types/d3-ease': 3.0.2 '@types/d3-interpolate': 3.0.4 - '@types/d3-scale': 4.0.8 + '@types/d3-scale': 4.0.9 '@types/d3-shape': 3.1.7 '@types/d3-time': 3.0.4 '@types/d3-timer': 3.0.2 From bea2f8633ad9caa5af494cfd4ac129417059598d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:27:04 +0000 Subject: [PATCH 060/255] chore: bump undici from 6.21.3 to 6.22.0 in /site (#20631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [undici](https://github.com/nodejs/undici) from 6.21.3 to 6.22.0.
Release notes

Sourced from undici's releases.

v6.22.0

What's Changed

Full Changelog: https://github.com/nodejs/undici/compare/v6.21.3...v6.22.0

Commits
  • f9c9185 Bumped v6.22.0
  • f670f2a feat: make UndiciErrors reliable to instanceof (#4472) (#4480)
  • 422e397 feat(ProxyAgent) improve Curl-y behavior in HTTP->HTTP Proxy connections (#41...
  • 4a06ffe feat(ProxyAgent): match Curl behavior in HTTP->HTTP Proxy connections (#4180)...
  • 4cb3974 fix: fix EnvHttpProxyAgent for the Node.js bundle (#4064) (#4432)
  • 44c23e5 fix: fix wrong stream canceled up after cloning (v6) (#4414)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=undici&package-manager=npm_and_yarn&previous-version=6.21.3&new-version=6.22.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/package.json b/site/package.json index 98b1b56bb72f8..0b66648365b0e 100644 --- a/site/package.json +++ b/site/package.json @@ -115,7 +115,7 @@ "tzdata": "1.0.46", "ua-parser-js": "1.0.41", "ufuzzy": "npm:@leeoniya/ufuzzy@1.0.10", - "undici": "6.21.3", + "undici": "6.22.0", "unique-names-generator": "4.7.1", "uuid": "9.0.1", "websocket-ts": "2.2.1", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 8db76c1c2b45b..497e4c2405df4 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -260,8 +260,8 @@ importers: specifier: npm:@leeoniya/ufuzzy@1.0.10 version: '@leeoniya/ufuzzy@1.0.10' undici: - specifier: 6.21.3 - version: 6.21.3 + specifier: 6.22.0 + version: 6.22.0 unique-names-generator: specifier: 4.7.1 version: 4.7.1 @@ -6137,8 +6137,8 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz} - undici@6.21.3: - resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==, tarball: https://registry.npmjs.org/undici/-/undici-6.21.3.tgz} + undici@6.22.0: + resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==, tarball: https://registry.npmjs.org/undici/-/undici-6.22.0.tgz} engines: {node: '>=18.17'} unicorn-magic@0.1.0: @@ -12967,7 +12967,7 @@ snapshots: undici-types@6.21.0: {} - undici@6.21.3: {} + undici@6.22.0: {} unicorn-magic@0.1.0: {} From c571995a424e034d84c5fcde8b5380b5cefd657c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:31:35 +0000 Subject: [PATCH 061/255] chore: bump protobufjs from 7.4.0 to 7.5.4 in /site (#20635) Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 7.4.0 to 7.5.4.
Release notes

Sourced from protobufjs's releases.

protobufjs: v7.5.4

7.5.4 (2025-08-15)

Bug Fixes

protobufjs: v7.5.3

7.5.3 (2025-05-28)

Bug Fixes

  • descriptor extensions handling post-editions (#2075) (6e255d4)

protobufjs: v7.5.2

7.5.2 (2025-05-14)

Bug Fixes

protobufjs: v7.5.1

7.5.1 (2025-05-08)

Bug Fixes

  • optimize regressions from editions implementations (#2066) (6406d4c)
  • reserved field inside group blocks fail parsing (#2058) (56782bf)

protobufjs: v7.5.0

7.5.0 (2025-04-15)

Features

  • add Edition 2023 Support (f04ded3)
  • add Edition 2023 Support (ac9a3b9)
  • add Edition 2023 Support (e5ca5c8)
  • add Edition 2023 Support (a84409b)
  • add Edition 2023 Support (9c5a178)
  • add Edition 2023 Support (b2c6867)
  • add Edition 2023 Support (60f3e51)
  • add Edition 2023 Support (a656361)
  • add Edition 2023 Support (869a95b)
  • add Edition 2023 Support (b936af4)
  • add Edition 2023 Support (a938467)

... (truncated)

Changelog

Sourced from protobufjs's changelog.

7.5.4 (2025-08-15)

Bug Fixes

7.5.3 (2025-05-28)

Bug Fixes

  • descriptor extensions handling post-editions (#2075) (6e255d4)

7.5.2 (2025-05-14)

Bug Fixes

7.5.1 (2025-05-08)

Bug Fixes

  • optimize regressions from editions implementations (#2066) (6406d4c)
  • reserved field inside group blocks fail parsing (#2058) (56782bf)

7.5.0 (2025-04-15)

Features

  • add Edition 2023 Support (f04ded3)
  • add Edition 2023 Support (ac9a3b9)
  • add Edition 2023 Support (e5ca5c8)
  • add Edition 2023 Support (a84409b)
  • add Edition 2023 Support (9c5a178)
  • add Edition 2023 Support (b2c6867)
  • add Edition 2023 Support (60f3e51)
  • add Edition 2023 Support (a656361)
  • add Edition 2023 Support (869a95b)
  • add Edition 2023 Support (b936af4)
  • add Edition 2023 Support (a938467)
  • add Edition 2023 Support (1af8454)
  • add Edition 2023 Support (785416f)
  • add feature resolution (a9ffc8a)
  • add feature resolution and tests (68b5339)
  • add feature resolution for protobuf editions (547afa2)

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=protobufjs&package-manager=npm_and_yarn&previous-version=7.4.0&new-version=7.5.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 29 ++++++++++++----------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/site/package.json b/site/package.json index 0b66648365b0e..60c58d87e15db 100644 --- a/site/package.json +++ b/site/package.json @@ -171,7 +171,7 @@ "knip": "5.64.1", "msw": "2.4.8", "postcss": "8.5.6", - "protobufjs": "7.4.0", + "protobufjs": "7.5.4", "rollup-plugin-visualizer": "5.14.0", "rxjs": "7.8.2", "ssh2": "1.17.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 497e4c2405df4..e10ce3ee7cd83 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -423,8 +423,8 @@ importers: specifier: 8.5.6 version: 8.5.6 protobufjs: - specifier: 7.4.0 - version: 7.4.0 + specifier: 7.5.4 + version: 7.5.4 rollup-plugin-visualizer: specifier: 5.14.0 version: 5.14.0(rollup@4.52.5) @@ -2704,8 +2704,8 @@ packages: '@types/node@20.17.16': resolution: {integrity: sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==, tarball: https://registry.npmjs.org/@types/node/-/node-20.17.16.tgz} - '@types/node@22.18.8': - resolution: {integrity: sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==, tarball: https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz} + '@types/node@22.18.13': + resolution: {integrity: sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==, tarball: https://registry.npmjs.org/@types/node/-/node-22.18.13.tgz} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==, tarball: https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz} @@ -4710,9 +4710,6 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==, tarball: https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz} engines: {node: '>=10'} - long@5.2.3: - resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==, tarball: https://registry.npmjs.org/long/-/long-5.2.3.tgz} - long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==, tarball: https://registry.npmjs.org/long/-/long-5.3.2.tgz} @@ -5342,8 +5339,8 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==, tarball: https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz} - protobufjs@7.4.0: - resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==, tarball: https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz} + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==, tarball: https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz} engines: {node: '>=12.0.0'} proxy-addr@2.0.7: @@ -7293,7 +7290,7 @@ snapshots: '@inquirer/figures': 1.0.13 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.18.8 + '@types/node': 22.18.13 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -8857,7 +8854,7 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/node@22.18.8': + '@types/node@22.18.13': dependencies: undici-types: 6.21.0 @@ -11250,8 +11247,6 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 - long@5.2.3: {} - long@5.3.2: {} longest-streak@3.1.0: {} @@ -12067,7 +12062,7 @@ snapshots: property-information@7.1.0: {} - protobufjs@7.4.0: + protobufjs@7.5.4: dependencies: '@protobufjs/aspromise': 1.1.2 '@protobufjs/base64': 1.1.2 @@ -12080,7 +12075,7 @@ snapshots: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 '@types/node': 20.17.16 - long: 5.2.3 + long: 5.3.2 proxy-addr@2.0.7: dependencies: @@ -12911,12 +12906,12 @@ snapshots: ts-proto-descriptors@1.16.0: dependencies: long: 5.3.2 - protobufjs: 7.4.0 + protobufjs: 7.5.4 ts-proto@1.181.2: dependencies: case-anything: 2.1.13 - protobufjs: 7.4.0 + protobufjs: 7.5.4 ts-poet: 6.12.0 ts-proto-descriptors: 1.16.0 From 279288affe4e49d773ccab3f2c0113ed3080a5ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:32:10 +0000 Subject: [PATCH 062/255] chore: bump knip from 5.64.1 to 5.66.4 in /site (#20636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 5.64.1 to 5.66.4.
Release notes

Sourced from knip's releases.

Release 5.66.4

  • Add experimental nextjs conventions support (#1322) (b7acf1fc7038f31797f82ec55a007cb73e9af08c) - thanks @​vinnymac!
  • Fix one character getting removed too much when fixing unused exported type (#1324) (935cf5d21d75ab19fd4783efe536a14a27bd9d6b) - thanks @​ulrichstark!
  • Set --fix if --fix-types or --allow-remove-files is set (close #1325) (d4b56e721c59f80c30ccd74c76f45cdeb9361dfa)
  • Update sponsors page (87c388047fde4e81ea39c3b8bbada61e51f8da7c)
  • Re-gen plugins list (a7d1ece38157ed7c1b177e0bf1ad3fed0fe63c37)
  • Update oxc-resolver (close #1316) (3eaad532be46d12c46ea6b80352216e4e355ec4e)

Release 5.66.3

  • feat(next): add proxy to entry file pattern (#1318) (c730727babd1321c5c1037178651113360ed38bc) - thanks @​filipweilid!
  • Add new vitest built-in reporters (#1320) (3bfdc80de8fe4e8a2d74ab99669c011e4cce2162) - thanks @​ocavue!
  • Fix unwanted duplicates reports if disabled (8012b548fe344540d6db1b5a9e7bfe24b9f0e411)
  • Fix bug in import map updater (90fc72e44d02c3b0919dd8ac60ec67fd8ab38fe0)
  • Increase precision for named import pos (4eb6dd3636bd2fc2df473ae960c8c37f930099a1)
  • Turn off rule if that issue type is disabled (4bc66d87396cea4dc079163b06bef9c4415cea21)
  • Move types (b7cf6aa0d2458e948b2066f726f49022d2683c50)
  • Get text of element.name (resolves #1315) (c39e7757c0e87d98a0601a202fecff8bd0e0384f)

Release 5.66.2

  • Fix negated patterns from package.json#exports (related to #1308) (2464f3704a11b0c6d1f71a1850f4fa928e6c623f)
  • Entries in rsbuild config are production entries (resolves #1309) (9eebc5574aa964f12a91f9bc8bb415f79c35aeed)
  • Add label for entry paths from package.json (42370b27eff932c25d2abfabb5313b20a65fbed5)

Release 5.66.1

  • Revive some tests in Node (20690d196775e8391dd50ae23398e57e8bd74267)
  • Fix up SymbolType and reuse SYMBOL_TYPE (resolves #1306) (d7c1c8313c751419588c0bec3e5e3b1f7e636ba0)
  • Minor refactor (3143c4e40303f1a1001035a04c41da14ccdb42f6)
  • Make defineNuxtConfig writable and deletable (resolves #1307) (c31a77f923452b4df88fe9a2bb9914ee400afbfd)
  • Fix up progress flag (c761a9d3647be2f7910c6992377695582e6a2d1e)
  • Clear screen in watch mode (fb3ff4e9d7e6a466312d290f01ff68adc70e4276)
  • Refactor watch mode (661440e8c822894e889524d5df5e0f9220c1c8be)
  • Re-play previously unretained issues in watch mode (9b96730aaa35bcfa13c210c1fba6485595918d03)
  • Format & lint (7776ae839f85c6d454894f019c79c3a0bfca2a3d)

Release 5.66.0

  • Add coverage for ignoreFiles feat (87ca476cdc1ebcc7637e2ff17a88e4fd7dfe790d)
  • update eleventy API to add addBundle() fix (#1300) (ed2acecbdbcf3eece05c4e5777ac5bb4f3620e06) - thanks @​hoardinghopes!
  • feat: add danger plugin (#1302) (d9e969da0eefce9c7e0060eb352aef8250f2004e) - thanks @​what1s1ove!
  • feat: add support for ignoring specific issue types per file pattern (#1303) (673893ac5cc1342ec85ca468ffeaff6ac239239c) - thanks @​rfalke-rtl!
  • Speed up JSON load (83ca88f4c007402d3a0b2b479b81a292ca76af5b)
  • Add JSON5 explainer to error (closes #1297) (cb926ca9eaec6b03b218ed76f06b690a13db2485)
  • Add ignoreIssues to JSON Schema (90056915e49be7b36a03cb35ec563876110d16c9)
  • Update docs (b4b89299399fa089ab85b8ea432b4cb753e11964)
  • Oh, CI (b153f93143b54288afaee09d626b43d9d6803c44)
  • Fix lint issues (0ccfda67af6190b8184ef6fe94036e79c9a06f1d)

Release 5.65.0

  • Release 5.64.3 (157ae943fa2a7b16321c1c6c5fff87ba9d6f3566)
  • Oops (f7ce7d7a0fed6acd4d22d8825dc3de08bff5df15)
  • Fix some typos in docs and code comments (#1299) (715d7cc75f4349547fba049839b4dca253acf57f) - thanks @​jdufresne!

... (truncated)

Commits
  • 2d44390 Release 5.66.4
  • 3eaad53 Update oxc-resolver (close #1316)
  • d4b56e7 Set --fix if --fix-types or --allow-remove-files is set (close #1325)
  • 935cf5d Fix one character getting removed too much when fixing unused exported type (...
  • b7acf1f Add experimental nextjs conventions support (#1322)
  • 9b1a40f Release 5.66.3
  • c39e775 Get text of element.name (resolves #1315)
  • b7cf6aa Move types
  • 4bc66d8 Turn off rule if that issue type is disabled
  • 4eb6dd3 Increase precision for named import pos
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=knip&package-manager=npm_and_yarn&previous-version=5.64.1&new-version=5.66.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 211 +++++++++++++++++++++----------------------- 2 files changed, 102 insertions(+), 111 deletions(-) diff --git a/site/package.json b/site/package.json index 60c58d87e15db..d05bb9b46d7a4 100644 --- a/site/package.json +++ b/site/package.json @@ -168,7 +168,7 @@ "jest-websocket-mock": "2.5.0", "jest_workaround": "0.1.14", "jsdom": "27.0.1", - "knip": "5.64.1", + "knip": "5.66.4", "msw": "2.4.8", "postcss": "8.5.6", "protobufjs": "7.5.4", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index e10ce3ee7cd83..51da674d40397 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -414,8 +414,8 @@ importers: specifier: 27.0.1 version: 27.0.1 knip: - specifier: 5.64.1 - version: 5.64.1(@types/node@20.17.16)(typescript@5.6.3) + specifier: 5.66.4 + version: 5.66.4(@types/node@20.17.16)(typescript@5.6.3) msw: specifier: 2.4.8 version: 2.4.8(typescript@5.6.3) @@ -778,11 +778,11 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==, tarball: https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz} engines: {node: '>=18'} - '@emnapi/core@1.5.0': - resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==, tarball: https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz} + '@emnapi/core@1.6.0': + resolution: {integrity: sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==, tarball: https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz} - '@emnapi/runtime@1.5.0': - resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==, tarball: https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz} + '@emnapi/runtime@1.6.0': + resolution: {integrity: sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==, tarball: https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==, tarball: https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz} @@ -1507,8 +1507,8 @@ packages: '@emotion/styled': optional: true - '@napi-rs/wasm-runtime@1.0.5': - resolution: {integrity: sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==, tarball: https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz} + '@napi-rs/wasm-runtime@1.0.7': + resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==, tarball: https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz} '@neoconfetti/react@1.0.0': resolution: {integrity: sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==, tarball: https://registry.npmjs.org/@neoconfetti/react/-/react-1.0.0.tgz} @@ -1540,98 +1540,98 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==, tarball: https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz} - '@oxc-resolver/binding-android-arm-eabi@11.8.4': - resolution: {integrity: sha512-6BjMji0TcvQfJ4EoSunOSyu/SiyHKficBD0V3Y0NxF0beaNnnZ7GYEi2lHmRNnRCuIPK8IuVqQ6XizYau+CkKw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.8.4.tgz} + '@oxc-resolver/binding-android-arm-eabi@11.12.0': + resolution: {integrity: sha512-/IfGWLNdmS1kVYM2g+Xw4qXNWtCPZ/i5YMprflA8FC3vAjT4N0VucQcDxUPHxatQwre4qnhbFFWqRa1mz6Cgkw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.12.0.tgz} cpu: [arm] os: [android] - '@oxc-resolver/binding-android-arm64@11.8.4': - resolution: {integrity: sha512-SxF4X6rzCBS9XNPXKZGoIHIABjfGmtQpEgRBDzpDHx5VTuLAUmwLTHXnVBAZoX5bmnhF79RiMElavzFdJ2cA1A==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.8.4.tgz} + '@oxc-resolver/binding-android-arm64@11.12.0': + resolution: {integrity: sha512-H3Ehyinfx2VO8F5TwdaD/WY686Ia6J1H3LP0tgpNjlPGH2TrTniPERiwjqtOm/xHEef0KJvb/yfmUKLbHudhCA==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.12.0.tgz} cpu: [arm64] os: [android] - '@oxc-resolver/binding-darwin-arm64@11.8.4': - resolution: {integrity: sha512-8zWeERrzgscAniE6kh1TQ4E7GJyglYsvdoKrHYLBCbHWD+0/soffiwAYxZuckKEQSc2RXMSPjcu+JTCALaY0Dw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.8.4.tgz} + '@oxc-resolver/binding-darwin-arm64@11.12.0': + resolution: {integrity: sha512-hmm+A/0WdEtIeBrPtUHoSTzJefrZkhGSrmv5pwELKiqNqd+/gctzmTlt6wWrU8BMIryDMT9fWqLSQ3+NYfqAEA==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.12.0.tgz} cpu: [arm64] os: [darwin] - '@oxc-resolver/binding-darwin-x64@11.8.4': - resolution: {integrity: sha512-BUwggKz8Hi5uEQ0AeVTSun1+sp4lzNcItn+L7fDsHu5Cx0Zueuo10BtVm+dIwmYVVPL5oGYOeD0fS7MKAazKiw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.8.4.tgz} + '@oxc-resolver/binding-darwin-x64@11.12.0': + resolution: {integrity: sha512-g1tVu53EMfuRKs67o0PZR0+y/WXl/Tfn3d2ggjK3Hj17pQPcb9x1+Y6W7n4EjIDttwLZbCPCEr06X+aC03I45A==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.12.0.tgz} cpu: [x64] os: [darwin] - '@oxc-resolver/binding-freebsd-x64@11.8.4': - resolution: {integrity: sha512-fPO5TQhnn8gA6yP4o49lc4Gn8KeDwAp9uYd4PlE3Q00JVqU6cY9WecDhYHrWtiFcyoZ8UVBlIxuhRqT/DP4Z4A==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.8.4.tgz} + '@oxc-resolver/binding-freebsd-x64@11.12.0': + resolution: {integrity: sha512-TiMatzvcVMSOiAx8sbnAw7UCfQpZDlm91ItywZrSHlQIJqDBipOmjIEYUMc2p823Y+fJ2ADL5UBjUB2kfqpedw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.12.0.tgz} cpu: [x64] os: [freebsd] - '@oxc-resolver/binding-linux-arm-gnueabihf@11.8.4': - resolution: {integrity: sha512-QuNbdUaVGiP0W0GrXsvCDZjqeL4lZGU7aXlx/S2tCvyTk3wh6skoiLJgqUf/eeqXfUPnzTfntYqyfolzCAyBYA==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.8.4.tgz} + '@oxc-resolver/binding-linux-arm-gnueabihf@11.12.0': + resolution: {integrity: sha512-zU+9UgxPIvfReqmRr/dqZt3387HPgcH0hA4u0QGE+280EFjBYYL2rxGDxK0L+keO6vc2+ITWVDXm9KIj+alofg==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.12.0.tgz} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm-musleabihf@11.8.4': - resolution: {integrity: sha512-p/zLMfza8OsC4BDKxqeZ9Qel+4eA/oiMSyKLRkMrTgt6OWQq1d5nHntjfG35Abcw4ev6Q9lRU3NOW5hj0xlUbw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.8.4.tgz} + '@oxc-resolver/binding-linux-arm-musleabihf@11.12.0': + resolution: {integrity: sha512-dfO1rrOeELYWD/BewMCp81k1I3pOdtAi2VCKg/A1I8z0uI4OR6cThb5dV9fpHkj7zlb0Y5iZFPe+NTbI/u1MgQ==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.12.0.tgz} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm64-gnu@11.8.4': - resolution: {integrity: sha512-bvJF9wWxF1+a5YZATlS5JojpOMC7OsnTatA6sXVHoOb7MIigjledYB5ZMAeRrnWWexRMiEX3YSaA46oSfOzmOg==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.8.4.tgz} + '@oxc-resolver/binding-linux-arm64-gnu@11.12.0': + resolution: {integrity: sha512-JJNyN1ueryETKTUsG57+u0GDbtHKVcwcUoC6YyJmDdWE0o/3twXtHuS+F/121a2sVK8PKlROqGAev+STx3AuuQ==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.12.0.tgz} cpu: [arm64] os: [linux] - '@oxc-resolver/binding-linux-arm64-musl@11.8.4': - resolution: {integrity: sha512-gf4nwGBfu+EFwOn5p7/T7VF4jmIdfodwJS9MRkOBHvuAm3LQgCX7O6d3Y80mm0TV7ZMRD/trfW628rHfd5++vQ==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.8.4.tgz} + '@oxc-resolver/binding-linux-arm64-musl@11.12.0': + resolution: {integrity: sha512-rQHoxL0H0WwYUuukPUscLyzWwTl/hyogptYsY+Ye6AggJEOuvgJxMum2glY7etGIGOXxrfjareHnNO1tNY7WYg==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.12.0.tgz} cpu: [arm64] os: [linux] - '@oxc-resolver/binding-linux-ppc64-gnu@11.8.4': - resolution: {integrity: sha512-T120R5GIzRd41rYWWKCI6cSYrZjmRQzf3X4xeE1WX396Uabz5DX8KU7RnVHihSK+KDxccCVOFBxcH3ITd+IEpw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.8.4.tgz} + '@oxc-resolver/binding-linux-ppc64-gnu@11.12.0': + resolution: {integrity: sha512-XPUZSctO+FrC0314Tcth+GrTtzy2yaYqyl8weBMAbKFMwuV8VnR2SHg9dmtI9vkukmM3auOLj0Kqjpl3YXwXiw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.12.0.tgz} cpu: [ppc64] os: [linux] - '@oxc-resolver/binding-linux-riscv64-gnu@11.8.4': - resolution: {integrity: sha512-PVG7SxBFFjAaQ76p9O/0Xt5mTBlziRwpck+6cRNhy/hbWY/hSt8BFfPqw0EDSfnl40Uuh+NPsHFMnaWWyxbQEg==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.8.4.tgz} + '@oxc-resolver/binding-linux-riscv64-gnu@11.12.0': + resolution: {integrity: sha512-AmMjcP+6zHLF1JNq/p3yPEcXmZW/Xw5Xl19Zd0eBCSyGORJRuUOkcnyC8bwMO43b/G7PtausB83fclnFL5KZ3w==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.12.0.tgz} cpu: [riscv64] os: [linux] - '@oxc-resolver/binding-linux-riscv64-musl@11.8.4': - resolution: {integrity: sha512-L0OklUhM2qLGaKvPSyKmwWpoijfc++VJtPyVgz031ShOXyo0WjD0ZGzusyJMsA1a/gdulAmN6CQ/0Sf4LGXEcw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.8.4.tgz} + '@oxc-resolver/binding-linux-riscv64-musl@11.12.0': + resolution: {integrity: sha512-K2/yFBqFQOKyVwQxYDAKqDtk2kS4g58aGyj/R1bvYPr2P7v7971aUG/5m2WD5u2zSqWBfu1o4PdhX0lsqvA3vQ==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.12.0.tgz} cpu: [riscv64] os: [linux] - '@oxc-resolver/binding-linux-s390x-gnu@11.8.4': - resolution: {integrity: sha512-18Ajz5hqO4cRGuoHzLFUsIPod9GIaIRDiXFg2m6CS3NgVdHx7iCZscplYH7KtjdE42M8nGWYMyyq5BOk7QVgPw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.8.4.tgz} + '@oxc-resolver/binding-linux-s390x-gnu@11.12.0': + resolution: {integrity: sha512-uSl4jo78tONGZtwsOA4ldT/OI7/hoHJhSMlGYE4Z/lzwMjkAaBdX4soAK5P/rL+U2yCJlRMnnoUckhXlZvDbSw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.12.0.tgz} cpu: [s390x] os: [linux] - '@oxc-resolver/binding-linux-x64-gnu@11.8.4': - resolution: {integrity: sha512-uHvH4RyYBdQ/lFGV9H+R1ScHg6EBnAhE3mnX+u+mO/btnalvg7j80okuHf8Qw0tLQiP5P1sEBoVeE6zviXY9IA==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.8.4.tgz} + '@oxc-resolver/binding-linux-x64-gnu@11.12.0': + resolution: {integrity: sha512-YjL8VAkbPyQ1kUuR6pOBk1O+EkxOoLROTa+ia1/AmFLuXYNltLGI1YxOY14i80cKpOf0Z59IXnlrY3coAI9NDQ==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.12.0.tgz} cpu: [x64] os: [linux] - '@oxc-resolver/binding-linux-x64-musl@11.8.4': - resolution: {integrity: sha512-X5z44qh5DdJfVhcqXAQFTDFUpcxdpf6DT/lHL5CFcdQGIZxatjc7gFUy05IXPI9xwfq39RValjJBvFovUk9XBw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.8.4.tgz} + '@oxc-resolver/binding-linux-x64-musl@11.12.0': + resolution: {integrity: sha512-qpHPU0qqeJXh7cPzA+I+WWA6RxtRArfmSrhTXidbiQ08G5A1e55YQwExWkitB2rSqN6YFxnpfhHKo9hyhpyfSg==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.12.0.tgz} cpu: [x64] os: [linux] - '@oxc-resolver/binding-wasm32-wasi@11.8.4': - resolution: {integrity: sha512-z3906y+cd8RRhBGNwHRrRAFxnKjXsBeL3+rdQjZpBrUyrhhsaV5iKD/ROx64FNJ9GjL/9mfon8A5xx/McYIqHA==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.8.4.tgz} + '@oxc-resolver/binding-wasm32-wasi@11.12.0': + resolution: {integrity: sha512-oqg80bERZAagWLqYmngnesE0/2miv4lST7+wiiZniD6gyb1SoRckwEkbTsytGutkudFtw7O61Pon6pNlOvyFaA==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.12.0.tgz} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@oxc-resolver/binding-win32-arm64-msvc@11.8.4': - resolution: {integrity: sha512-70vXFs74uA3X5iYOkpclbkWlQEF+MI325uAQ+Or2n8HJip2T0SEmuBlyw/sRL2E8zLC4oocb+1g25fmzlDVkmg==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.8.4.tgz} + '@oxc-resolver/binding-win32-arm64-msvc@11.12.0': + resolution: {integrity: sha512-qKH816ycEN9yR/TX91CP1/i6xyVNHKX9VEOYa3XzQROPVtcYG2F6A3ng/PhwpJvS1cmL/DlilhglZe9KWkhNjg==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.12.0.tgz} cpu: [arm64] os: [win32] - '@oxc-resolver/binding-win32-ia32-msvc@11.8.4': - resolution: {integrity: sha512-SEOUAzTvr+nyMia3nx1dMtD7YUxZwuhQ3QAPnxy21261Lj0yT3JY4EIfwWH54lAWWfMdRSRRMFuGeF/dq7XjEw==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.8.4.tgz} + '@oxc-resolver/binding-win32-ia32-msvc@11.12.0': + resolution: {integrity: sha512-3bgxubTlhzF6BwBnhGz5BTboarl1upuanEr6i0dncjfEcU+Z9xAOgbtA7Ip3G3EKDjE1objRKK+ny8PKJZ3b7Q==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.12.0.tgz} cpu: [ia32] os: [win32] - '@oxc-resolver/binding-win32-x64-msvc@11.8.4': - resolution: {integrity: sha512-1gARIQsOPOU7LJ7jvMyPmZEVMapL/PymeG3J7naOdLZDrIZKX6CTvgawJmETYKt+8icP8M6KbBinrVkKVqFd+A==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.8.4.tgz} + '@oxc-resolver/binding-win32-x64-msvc@11.12.0': + resolution: {integrity: sha512-rbiWYQWxwy+x7+KgNAoAGYIPB3xUclQlFVV3L5lwfsbp4PQPomJohHowlWgi3GRAEybM5+ZL9xny0YfpJOsthA==, tarball: https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.12.0.tgz} cpu: [x64] os: [win32] @@ -4653,8 +4653,8 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==, tarball: https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz} engines: {node: '>=6'} - knip@5.64.1: - resolution: {integrity: sha512-80XnLsyeXuyxj1F4+NBtQFHxaRH0xWRw8EKwfQ6EkVZZ0bSz/kqqan08k/Qg8ajWsFPhFq+0S2RbLCBGIQtuOg==, tarball: https://registry.npmjs.org/knip/-/knip-5.64.1.tgz} + knip@5.66.4: + resolution: {integrity: sha512-HmTnxdmoHAvwKmFktRGY1++tXRI8J36eVrOpfj/ybTVVT1QBKBlbBEN1s3cJBx9UL+hXTZDNQif+gs7fUKldbw==, tarball: https://registry.npmjs.org/knip/-/knip-5.66.4.tgz} engines: {node: '>=18.18.0'} hasBin: true peerDependencies: @@ -5008,11 +5008,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-postinstall@0.3.3: - resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==, tarball: https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - hasBin: true - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, tarball: https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz} @@ -5098,8 +5093,8 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==, tarball: https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz} - oxc-resolver@11.8.4: - resolution: {integrity: sha512-qpimS3tHHEf+kgESMAme+q+rj7aCzMya00u9YdKOKyX2o7q4lozjPo6d7ZTTi979KHEcVOPWdNTueAKdeNq72w==, tarball: https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.8.4.tgz} + oxc-resolver@11.12.0: + resolution: {integrity: sha512-zmS2q2txiB+hS2u0aiIwmvITIJN8c8ThlWoWB762Wx5nUw8WBlttp0rzt8nnuP1cGIq9YJ7sGxfsgokm+SQk5Q==, tarball: https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.12.0.tgz} p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, tarball: https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz} @@ -6596,8 +6591,8 @@ packages: yup@1.6.1: resolution: {integrity: sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==, tarball: https://registry.npmjs.org/yup/-/yup-1.6.1.tgz} - zod@4.1.11: - resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==, tarball: https://registry.npmjs.org/zod/-/zod-4.1.11.tgz} + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==, tarball: https://registry.npmjs.org/zod/-/zod-4.1.12.tgz} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, tarball: https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz} @@ -6938,13 +6933,13 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} - '@emnapi/core@1.5.0': + '@emnapi/core@1.6.0': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.5.0': + '@emnapi/runtime@1.6.0': dependencies: tslib: 2.8.1 optional: true @@ -7704,10 +7699,10 @@ snapshots: transitivePeerDependencies: - '@types/react' - '@napi-rs/wasm-runtime@1.0.5': + '@napi-rs/wasm-runtime@1.0.7': dependencies: - '@emnapi/core': 1.5.0 - '@emnapi/runtime': 1.5.0 + '@emnapi/core': 1.6.0 + '@emnapi/runtime': 1.6.0 '@tybys/wasm-util': 0.10.1 optional: true @@ -7740,63 +7735,63 @@ snapshots: '@open-draft/until@2.1.0': {} - '@oxc-resolver/binding-android-arm-eabi@11.8.4': + '@oxc-resolver/binding-android-arm-eabi@11.12.0': optional: true - '@oxc-resolver/binding-android-arm64@11.8.4': + '@oxc-resolver/binding-android-arm64@11.12.0': optional: true - '@oxc-resolver/binding-darwin-arm64@11.8.4': + '@oxc-resolver/binding-darwin-arm64@11.12.0': optional: true - '@oxc-resolver/binding-darwin-x64@11.8.4': + '@oxc-resolver/binding-darwin-x64@11.12.0': optional: true - '@oxc-resolver/binding-freebsd-x64@11.8.4': + '@oxc-resolver/binding-freebsd-x64@11.12.0': optional: true - '@oxc-resolver/binding-linux-arm-gnueabihf@11.8.4': + '@oxc-resolver/binding-linux-arm-gnueabihf@11.12.0': optional: true - '@oxc-resolver/binding-linux-arm-musleabihf@11.8.4': + '@oxc-resolver/binding-linux-arm-musleabihf@11.12.0': optional: true - '@oxc-resolver/binding-linux-arm64-gnu@11.8.4': + '@oxc-resolver/binding-linux-arm64-gnu@11.12.0': optional: true - '@oxc-resolver/binding-linux-arm64-musl@11.8.4': + '@oxc-resolver/binding-linux-arm64-musl@11.12.0': optional: true - '@oxc-resolver/binding-linux-ppc64-gnu@11.8.4': + '@oxc-resolver/binding-linux-ppc64-gnu@11.12.0': optional: true - '@oxc-resolver/binding-linux-riscv64-gnu@11.8.4': + '@oxc-resolver/binding-linux-riscv64-gnu@11.12.0': optional: true - '@oxc-resolver/binding-linux-riscv64-musl@11.8.4': + '@oxc-resolver/binding-linux-riscv64-musl@11.12.0': optional: true - '@oxc-resolver/binding-linux-s390x-gnu@11.8.4': + '@oxc-resolver/binding-linux-s390x-gnu@11.12.0': optional: true - '@oxc-resolver/binding-linux-x64-gnu@11.8.4': + '@oxc-resolver/binding-linux-x64-gnu@11.12.0': optional: true - '@oxc-resolver/binding-linux-x64-musl@11.8.4': + '@oxc-resolver/binding-linux-x64-musl@11.12.0': optional: true - '@oxc-resolver/binding-wasm32-wasi@11.8.4': + '@oxc-resolver/binding-wasm32-wasi@11.12.0': dependencies: - '@napi-rs/wasm-runtime': 1.0.5 + '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@oxc-resolver/binding-win32-arm64-msvc@11.8.4': + '@oxc-resolver/binding-win32-arm64-msvc@11.12.0': optional: true - '@oxc-resolver/binding-win32-ia32-msvc@11.8.4': + '@oxc-resolver/binding-win32-ia32-msvc@11.12.0': optional: true - '@oxc-resolver/binding-win32-x64-msvc@11.8.4': + '@oxc-resolver/binding-win32-x64-msvc@11.12.0': optional: true '@pkgjs/parseargs@0.11.0': @@ -11186,7 +11181,7 @@ snapshots: kleur@3.0.3: {} - knip@5.64.1(@types/node@20.17.16)(typescript@5.6.3): + knip@5.66.4(@types/node@20.17.16)(typescript@5.6.3): dependencies: '@nodelib/fs.walk': 1.2.8 '@types/node': 20.17.16 @@ -11195,13 +11190,13 @@ snapshots: jiti: 2.6.1 js-yaml: 4.1.0 minimist: 1.2.8 - oxc-resolver: 11.8.4 + oxc-resolver: 11.12.0 picocolors: 1.1.1 picomatch: 4.0.3 smol-toml: 1.4.2 strip-json-comments: 5.0.2 typescript: 5.6.3 - zod: 4.1.11 + zod: 4.1.12 leven@3.1.0: {} @@ -11739,8 +11734,6 @@ snapshots: nanoid@3.3.11: {} - napi-postinstall@0.3.3: {} - natural-compare@1.4.0: {} negotiator@0.6.3: {} @@ -11828,29 +11821,27 @@ snapshots: outvariant@1.4.3: {} - oxc-resolver@11.8.4: - dependencies: - napi-postinstall: 0.3.3 + oxc-resolver@11.12.0: optionalDependencies: - '@oxc-resolver/binding-android-arm-eabi': 11.8.4 - '@oxc-resolver/binding-android-arm64': 11.8.4 - '@oxc-resolver/binding-darwin-arm64': 11.8.4 - '@oxc-resolver/binding-darwin-x64': 11.8.4 - '@oxc-resolver/binding-freebsd-x64': 11.8.4 - '@oxc-resolver/binding-linux-arm-gnueabihf': 11.8.4 - '@oxc-resolver/binding-linux-arm-musleabihf': 11.8.4 - '@oxc-resolver/binding-linux-arm64-gnu': 11.8.4 - '@oxc-resolver/binding-linux-arm64-musl': 11.8.4 - '@oxc-resolver/binding-linux-ppc64-gnu': 11.8.4 - '@oxc-resolver/binding-linux-riscv64-gnu': 11.8.4 - '@oxc-resolver/binding-linux-riscv64-musl': 11.8.4 - '@oxc-resolver/binding-linux-s390x-gnu': 11.8.4 - '@oxc-resolver/binding-linux-x64-gnu': 11.8.4 - '@oxc-resolver/binding-linux-x64-musl': 11.8.4 - '@oxc-resolver/binding-wasm32-wasi': 11.8.4 - '@oxc-resolver/binding-win32-arm64-msvc': 11.8.4 - '@oxc-resolver/binding-win32-ia32-msvc': 11.8.4 - '@oxc-resolver/binding-win32-x64-msvc': 11.8.4 + '@oxc-resolver/binding-android-arm-eabi': 11.12.0 + '@oxc-resolver/binding-android-arm64': 11.12.0 + '@oxc-resolver/binding-darwin-arm64': 11.12.0 + '@oxc-resolver/binding-darwin-x64': 11.12.0 + '@oxc-resolver/binding-freebsd-x64': 11.12.0 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.12.0 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.12.0 + '@oxc-resolver/binding-linux-arm64-gnu': 11.12.0 + '@oxc-resolver/binding-linux-arm64-musl': 11.12.0 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.12.0 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.12.0 + '@oxc-resolver/binding-linux-riscv64-musl': 11.12.0 + '@oxc-resolver/binding-linux-s390x-gnu': 11.12.0 + '@oxc-resolver/binding-linux-x64-gnu': 11.12.0 + '@oxc-resolver/binding-linux-x64-musl': 11.12.0 + '@oxc-resolver/binding-wasm32-wasi': 11.12.0 + '@oxc-resolver/binding-win32-arm64-msvc': 11.12.0 + '@oxc-resolver/binding-win32-ia32-msvc': 11.12.0 + '@oxc-resolver/binding-win32-x64-msvc': 11.12.0 p-limit@2.3.0: dependencies: @@ -13356,6 +13347,6 @@ snapshots: toposort: 2.0.2 type-fest: 2.19.0 - zod@4.1.11: {} + zod@4.1.12: {} zwitch@2.0.4: {} From 17438d9730f4dd5e88a4ea84168e5576eb7e445e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:32:40 +0000 Subject: [PATCH 063/255] chore: bump react-router from 7.8.0 to 7.9.5 in /site (#20624) Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.8.0 to 7.9.5.
Release notes

Sourced from react-router's releases.

v7.9.5

See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v795

v7.9.4

See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v794

v7.9.3

See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v793

v7.9.2

See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v792

v7.9.1

See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v791

v7.9.0

See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v790

v7.8.2

See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v782

v7.8.1

See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v781

Changelog

Sourced from react-router's changelog.

7.9.5

Patch Changes

  • Move RSCHydratedRouter and utils to /dom export. (#14457)

  • useRoute: return type-safe handle (#14462)

    For example:

    // app/routes/admin.tsx
    const handle = { hello: "world" };
    
    // app/routes/some-other-route.tsx
    export default function Component() {
      const admin = useRoute("routes/admin");
    if (!admin) throw new Error("Not nested within
    'routes/admin'");
      console.log(admin.handle);
      //                ^? { hello: string }
    }
    
  • Ensure action handlers run for routes with middleware even if no loader is present (#14443)

  • Add unstable_instrumentations API to allow users to add observablity to their apps by instrumenting route loaders, actions, middlewares, lazy, as well as server-side request handlers and client side navigations/fetches (#14412)

    • Framework Mode:
      • entry.server.tsx: export const unstable_instrumentations = [...]
      • entry.client.tsx: <HydratedRouter unstable_instrumentations={[...]} />
    • Data Mode
      • createBrowserRouter(routes, { unstable_instrumentations: [...] })

    This also adds a new unstable_pattern parameter to loaders/actions/middleware which contains the un-interpolated route pattern (i.e., /blog/:slug) which is useful for aggregating performance metrics by route

7.9.4

Patch Changes

  • handle external redirects in from server actions (#14400)

  • New (unstable) useRoute hook for accessing data from specific routes (#14407)

    For example, let's say you have an admin route somewhere in your app and you want any child routes of admin to all have access to the loaderData and actionData from admin.

    // app/routes/admin.tsx
    import { Outlet } from "react-router";
    

    export const loader = () => ({ message: "Hello, loader!" });

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=react-router&package-manager=npm_and_yarn&previous-version=7.8.0&new-version=7.9.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/site/package.json b/site/package.json index d05bb9b46d7a4..518b1a17cf4da 100644 --- a/site/package.json +++ b/site/package.json @@ -101,7 +101,7 @@ "react-markdown": "9.1.0", "react-query": "npm:@tanstack/react-query@5.77.0", "react-resizable-panels": "3.0.6", - "react-router": "7.8.0", + "react-router": "7.9.5", "react-syntax-highlighter": "15.6.1", "react-textarea-autosize": "8.5.9", "react-virtualized-auto-sizer": "1.0.26", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 51da674d40397..68a3a26dfeda3 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -218,8 +218,8 @@ importers: specifier: 3.0.6 version: 3.0.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-router: - specifier: 7.8.0 - version: 7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 7.9.5 + version: 7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-syntax-highlighter: specifier: 15.6.1 version: 15.6.1(react@19.1.1) @@ -439,7 +439,7 @@ importers: version: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) storybook-addon-remix-react-router: specifier: 5.0.0 - version: 5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) tailwindcss: specifier: 3.4.18 version: 3.4.18(yaml@2.7.0) @@ -5465,8 +5465,8 @@ packages: react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - react-router@7.8.0: - resolution: {integrity: sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==, tarball: https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz} + react-router@7.9.5: + resolution: {integrity: sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==, tarball: https://registry.npmjs.org/react-router/-/react-router-7.9.5.tgz} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -5692,8 +5692,8 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==, tarball: https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz} engines: {node: '>= 0.8.0'} - set-cookie-parser@2.7.1: - resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==, tarball: https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz} + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==, tarball: https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz} set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, tarball: https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz} @@ -12210,11 +12210,11 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - react-router@7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: cookie: 1.0.2 react: 19.1.1 - set-cookie-parser: 2.7.1 + set-cookie-parser: 2.7.2 optionalDependencies: react-dom: 19.1.1(react@19.1.1) @@ -12509,7 +12509,7 @@ snapshots: transitivePeerDependencies: - supports-color - set-cookie-parser@2.7.1: {} + set-cookie-parser@2.7.2: {} set-function-length@1.2.2: dependencies: @@ -12623,12 +12623,12 @@ snapshots: dependencies: internal-slot: 1.0.6 - storybook-addon-remix-react-router@5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))): + storybook-addon-remix-react-router@5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))): dependencies: '@mjackson/form-data-parser': 0.4.0 compare-versions: 6.1.0 react-inspector: 6.0.2(react@19.1.1) - react-router: 7.8.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-router: 7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: react: 19.1.1 From 0b214ad7f66f2cf112788a4c2323bf94cf160440 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:33:04 +0000 Subject: [PATCH 064/255] chore: bump semver from 7.7.2 to 7.7.3 in /site (#20620) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps [semver](https://github.com/npm/node-semver) from 7.7.2 to 7.7.3.
Release notes

Sourced from semver's releases.

v7.7.3

7.7.3 (2025-10-06)

Bug Fixes

Chores

Changelog

Sourced from semver's changelog.

7.7.3 (2025-10-06)

Bug Fixes

Chores

Commits
  • a25789b chore: release 7.7.3 (#812)
  • e37e0ca fix: faster paths for compare (#813)
  • 2471d75 fix: x-range build metadata support
  • 8f05c87 chore: bump @​npmcli/template-oss from 4.25.0 to 4.25.1 (#807)
  • d17aebf chore: bump @​npmcli/template-oss from 4.24.4 to 4.25.0 (#797)
  • 3b03e3b chore: bump @​npmcli/template-oss from 4.24.3 to 4.24.4 (#790)
  • See full diff in compare view
Maintainer changes

This version was pushed to npm by [GitHub Actions](https://www.npmjs.com/~GitHub Actions), a new releaser for semver since your current version.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=semver&package-manager=npm_and_yarn&previous-version=7.7.2&new-version=7.7.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 4 ++-- site/pnpm-lock.yaml | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/site/package.json b/site/package.json index 518b1a17cf4da..51f1f39e5f7fe 100644 --- a/site/package.json +++ b/site/package.json @@ -109,7 +109,7 @@ "recharts": "2.15.4", "remark-gfm": "4.0.1", "resize-observer-polyfill": "1.5.1", - "semver": "7.7.2", + "semver": "7.7.3", "tailwind-merge": "2.6.0", "tailwindcss-animate": "1.0.7", "tzdata": "1.0.46", @@ -191,7 +191,7 @@ ], "resolutions": { "optionator": "0.9.3", - "semver": "7.7.2" + "semver": "7.7.3" }, "engines": { "pnpm": ">=10.0.0 <11.0.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 68a3a26dfeda3..bdb11dba97c77 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: optionator: 0.9.3 - semver: 7.7.2 + semver: 7.7.3 '@babel/runtime': 7.26.10 '@babel/helpers': 7.26.10 esbuild: ^0.25.0 @@ -242,8 +242,8 @@ importers: specifier: 1.5.1 version: 1.5.1 semver: - specifier: 7.7.2 - version: 7.7.2 + specifier: 7.7.3 + version: 7.7.3 tailwind-merge: specifier: 2.6.0 version: 2.6.0 @@ -5679,8 +5679,8 @@ packages: scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==, tarball: https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz} - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==, tarball: https://registry.npmjs.org/semver/-/semver-7.7.2.tgz} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==, tarball: https://registry.npmjs.org/semver/-/semver-7.7.3.tgz} engines: {node: '>=10'} hasBin: true @@ -6648,7 +6648,7 @@ snapshots: debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -6666,7 +6666,7 @@ snapshots: '@babel/helper-validator-option': 7.27.1 browserslist: 4.27.0 lru-cache: 5.1.1 - semver: 7.7.2 + semver: 7.7.3 '@babel/helper-globals@7.28.0': {} @@ -10646,7 +10646,7 @@ snapshots: '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -10656,7 +10656,7 @@ snapshots: '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -10998,7 +10998,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.7.2 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -11283,7 +11283,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 make-error@1.3.6: optional: true @@ -12480,7 +12480,7 @@ snapshots: scheduler@0.26.0: {} - semver@7.7.2: {} + semver@7.7.3: {} send@0.19.0: dependencies: @@ -12646,7 +12646,7 @@ snapshots: esbuild: 0.25.3 esbuild-register: 3.6.0(esbuild@0.25.3) recast: 0.23.9 - semver: 7.7.2 + semver: 7.7.3 ws: 8.18.0 optionalDependencies: prettier: 3.4.1 From acd6fe7aebc62913ac9c848c5aa1eeb72458a413 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:34:30 +0000 Subject: [PATCH 065/255] chore: bump @fontsource/source-code-pro from 5.2.5 to 5.2.7 in /site (#20632) Bumps [@fontsource/source-code-pro](https://github.com/fontsource/font-files/tree/HEAD/fonts/google/source-code-pro) from 5.2.5 to 5.2.7.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@fontsource/source-code-pro&package-manager=npm_and_yarn&previous-version=5.2.5&new-version=5.2.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/package.json b/site/package.json index 51f1f39e5f7fe..7135a76d32f5b 100644 --- a/site/package.json +++ b/site/package.json @@ -45,7 +45,7 @@ "@fontsource/fira-code": "5.2.7", "@fontsource/ibm-plex-mono": "5.2.7", "@fontsource/jetbrains-mono": "5.2.8", - "@fontsource/source-code-pro": "5.2.5", + "@fontsource/source-code-pro": "5.2.7", "@monaco-editor/react": "4.7.0", "@mui/material": "5.18.0", "@mui/system": "5.18.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index bdb11dba97c77..2c0d3e1de40c4 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -50,8 +50,8 @@ importers: specifier: 5.2.8 version: 5.2.8 '@fontsource/source-code-pro': - specifier: 5.2.5 - version: 5.2.5 + specifier: 5.2.7 + version: 5.2.7 '@monaco-editor/react': specifier: 4.7.0 version: 4.7.0(monaco-editor@0.53.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -1204,8 +1204,8 @@ packages: '@fontsource/jetbrains-mono@5.2.8': resolution: {integrity: sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==, tarball: https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz} - '@fontsource/source-code-pro@5.2.5': - resolution: {integrity: sha512-1k7b9IdhVSdK/rJ8CkqqGFZ01C3NaXNynPZqKaTetODog/GPJiMYd6E8z+LTwSUTIX8dm2QZORDC+Uh91cjXSg==, tarball: https://registry.npmjs.org/@fontsource/source-code-pro/-/source-code-pro-5.2.5.tgz} + '@fontsource/source-code-pro@5.2.7': + resolution: {integrity: sha512-7papq9TH94KT+S5VSY8cU7tFmwuGkIe3qxXRMscuAXH6AjMU+KJI75f28FzgBVDrlMfA0jjlTV4/x5+H5o/5EQ==, tarball: https://registry.npmjs.org/@fontsource/source-code-pro/-/source-code-pro-5.2.7.tgz} '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==, tarball: https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz} @@ -7254,7 +7254,7 @@ snapshots: '@fontsource/jetbrains-mono@5.2.8': {} - '@fontsource/source-code-pro@5.2.5': {} + '@fontsource/source-code-pro@5.2.7': {} '@humanwhocodes/config-array@0.11.14': dependencies: From 697b3a0a066377935eee5865dd95a013341dacb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:36:37 +0000 Subject: [PATCH 066/255] chore: bump cmdk from 1.0.4 to 1.1.1 in /site (#20630) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [cmdk](https://github.com/pacocoursey/cmdk/tree/HEAD/cmdk) from 1.0.4 to 1.1.1.
Release notes

Sourced from cmdk's releases.

v1.1.1

What's Changed

New Contributors

Full Changelog: https://github.com/pacocoursey/cmdk/compare/v1.1.0...v1.1.1

v1.1.0

What's Changed

New Contributors

Full Changelog: https://github.com/pacocoursey/cmdk/compare/v1.0.4...v1.1.0

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=cmdk&package-manager=npm_and_yarn&previous-version=1.0.4&new-version=1.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 109 ++++---------------------------------------- 2 files changed, 9 insertions(+), 102 deletions(-) diff --git a/site/package.json b/site/package.json index 7135a76d32f5b..1b8d8ec70a86f 100644 --- a/site/package.json +++ b/site/package.json @@ -78,7 +78,7 @@ "chroma-js": "2.6.0", "class-variance-authority": "0.7.1", "clsx": "2.1.1", - "cmdk": "1.0.4", + "cmdk": "1.1.1", "color-convert": "2.0.1", "cron-parser": "4.9.0", "cronstrue": "2.59.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 2c0d3e1de40c4..a13d970444f32 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -149,8 +149,8 @@ importers: specifier: 2.1.1 version: 2.1.1 cmdk: - specifier: 1.0.4 - version: 1.0.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: 1.1.1 + version: 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) color-convert: specifier: 2.0.1 version: 2.0.1 @@ -1748,15 +1748,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-compose-refs@1.1.1': - resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==, tarball: https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==, tarball: https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz} peerDependencies: @@ -1845,15 +1836,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-id@1.1.0': - resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==, tarball: https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==, tarball: https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz} peerDependencies: @@ -1941,19 +1923,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.0.1': - resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==, tarball: https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-primitive@2.1.3': resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==, tarball: https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz} peerDependencies: @@ -2045,15 +2014,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.1.1': - resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==, tarball: https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==, tarball: https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz} peerDependencies: @@ -2134,15 +2094,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-layout-effect@1.1.0': - resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==, tarball: https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-layout-effect@1.1.1': resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==, tarball: https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz} peerDependencies: @@ -3318,8 +3269,8 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, tarball: https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz} engines: {node: '>=6'} - cmdk@1.0.4: - resolution: {integrity: sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==, tarball: https://registry.npmjs.org/cmdk/-/cmdk-1.0.4.tgz} + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==, tarball: https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc @@ -6243,11 +6194,6 @@ packages: '@types/react': optional: true - use-sync-external-store@1.4.0: - resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==, tarball: https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==, tarball: https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz} peerDependencies: @@ -7896,12 +7842,6 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-compose-refs@1.1.1(@types/react@19.1.17)(react@19.1.1)': - dependencies: - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 @@ -7987,13 +7927,6 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-id@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-id@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) @@ -8097,15 +8030,6 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-primitive@2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-slot': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) @@ -8224,13 +8148,6 @@ snapshots: '@types/react': 19.1.17 '@types/react-dom': 19.1.11(@types/react@19.1.17) - '@radix-ui/react-slot@1.1.1(@types/react@19.1.17)(react@19.1.1)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-slot@1.2.3(@types/react@19.1.17)(react@19.1.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) @@ -8308,12 +8225,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.1.17)(react@19.1.1)': - dependencies: - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.17 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.17)(react@19.1.1)': dependencies: react: 19.1.1 @@ -9480,14 +9391,14 @@ snapshots: clsx@2.1.1: {} - cmdk@1.0.4(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + cmdk@1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - use-sync-external-store: 1.4.0(react@19.1.1) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -13063,10 +12974,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.17 - use-sync-external-store@1.4.0(react@19.1.1): - dependencies: - react: 19.1.1 - use-sync-external-store@1.6.0(react@19.1.1): dependencies: react: 19.1.1 From eb020611a3c02e874a1a2c17cf429d6451cd7a73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:44:05 +0000 Subject: [PATCH 067/255] chore: bump storybook from 9.1.2 to 9.1.16 in /site (#20627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/core) from 9.1.2 to 9.1.16.
Release notes

Sourced from storybook's releases.

v9.1.16

9.1.16

v9.1.15

9.1.15

v9.1.14

9.1.14

v9.1.12

9.1.12

  • Maintenance: Hotfix for missing nextjs dts files, thanks @​ndelangen!

v9.1.11

9.1.11

v9.1.10

9.1.10

v9.1.9

9.1.9

  • Angular: Enable experimental zoneless detection on Angular v21 - #32580, thanks @​yannbf!
  • Svelte: Ignore inherited HTMLAttributes docgen when using utility types - #32173, thanks @​steciuk!

v9.1.8

9.1.8

... (truncated)

Changelog

Sourced from storybook's changelog.

9.1.16

9.1.15

9.1.14

9.1.13

9.1.12

  • Maintenance: Hotfix for missing nextjs dts files, thanks @​ndelangen!

9.1.11

9.1.10

9.1.9

  • Angular: Enable experimental zoneless detection on Angular v21 - #32580, thanks @​yannbf!
  • Svelte: Ignore inherited HTMLAttributes docgen when using utility types - #32173, thanks @​steciuk!

9.1.8

9.1.7

  • Dependencies: Update vite-plugin-storybook-nextjs to 2.0.7 - #32331, thanks @​k35o!

... (truncated)

Commits
  • a54a04c Bump version from "9.1.15" to "9.1.16" [skip ci]
  • ebd7ff5 Merge pull request #32859 from storybookjs/shilman/first-load-new-user
  • da2da6e Merge pull request #32862 from storybookjs/yann/patch-dev-server-preset
  • d0d17d9 Bump version from "9.1.14" to "9.1.15" [skip ci]
  • b3129cd fix exports
  • a78540a Merge pull request #32770 from storybookjs/shilman/preview-first-load
  • 5afb39f Bump version from "9.1.13" to "9.1.14" [skip ci]
  • 0617aaa improve typings of storybook/internal/babel
  • 5a70f04 Merge pull request #32819 from storybookjs/valentin/vitest-v4-support-2
  • cae3815 Merge pull request #32695 from storybookjs/shilman/32687-play-method
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by [GitHub Actions](https://www.npmjs.com/~GitHub Actions), a new releaser for storybook since your current version.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=storybook&package-manager=npm_and_yarn&previous-version=9.1.2&new-version=9.1.16)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 423 +++++++------------------------------------- 2 files changed, 66 insertions(+), 359 deletions(-) diff --git a/site/package.json b/site/package.json index 1b8d8ec70a86f..49b1baee05ab4 100644 --- a/site/package.json +++ b/site/package.json @@ -175,7 +175,7 @@ "rollup-plugin-visualizer": "5.14.0", "rxjs": "7.8.2", "ssh2": "1.17.0", - "storybook": "9.1.2", + "storybook": "9.1.16", "storybook-addon-remix-react-router": "5.0.0", "tailwindcss": "3.4.18", "ts-proto": "1.181.2", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index a13d970444f32..49b36a8b1e753 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -280,7 +280,7 @@ importers: version: 2.2.4 '@chromatic-com/storybook': specifier: 4.1.0 - version: 4.1.0(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 4.1.0(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@octokit/types': specifier: 12.6.0 version: 12.6.0 @@ -289,16 +289,16 @@ importers: version: 1.50.1 '@storybook/addon-docs': specifier: 9.1.2 - version: 9.1.2(@types/react@19.1.17)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 9.1.2(@types/react@19.1.17)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/addon-links': specifier: 9.1.2 - version: 9.1.2(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 9.1.2(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/addon-themes': specifier: 9.1.2 - version: 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/react-vite': specifier: 9.1.2 - version: 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + version: 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) '@swc/core': specifier: 1.3.38 version: 1.3.38 @@ -435,11 +435,11 @@ importers: specifier: 1.17.0 version: 1.17.0 storybook: - specifier: 9.1.2 - version: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + specifier: 9.1.16 + version: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) storybook-addon-remix-react-router: specifier: 5.0.0 - version: 5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) tailwindcss: specifier: 3.4.18 version: 3.4.18(yaml@2.7.0) @@ -859,252 +859,126 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.25.3': - resolution: {integrity: sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/android-arm64@0.25.11': resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.25.3': - resolution: {integrity: sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm@0.25.11': resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.25.3': - resolution: {integrity: sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-x64@0.25.11': resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.25.3': - resolution: {integrity: sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/darwin-arm64@0.25.11': resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.25.3': - resolution: {integrity: sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-x64@0.25.11': resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.25.3': - resolution: {integrity: sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/freebsd-arm64@0.25.11': resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.25.3': - resolution: {integrity: sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-x64@0.25.11': resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.3': - resolution: {integrity: sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/linux-arm64@0.25.11': resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.25.3': - resolution: {integrity: sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm@0.25.11': resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.25.3': - resolution: {integrity: sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-ia32@0.25.11': resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.25.3': - resolution: {integrity: sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-loong64@0.25.11': resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.25.3': - resolution: {integrity: sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-mips64el@0.25.11': resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.25.3': - resolution: {integrity: sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-ppc64@0.25.11': resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.25.3': - resolution: {integrity: sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-riscv64@0.25.11': resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.25.3': - resolution: {integrity: sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-s390x@0.25.11': resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.25.3': - resolution: {integrity: sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-x64@0.25.11': resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.25.3': - resolution: {integrity: sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/netbsd-arm64@0.25.11': resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.25.3': - resolution: {integrity: sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-x64@0.25.11': resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.3': - resolution: {integrity: sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/openbsd-arm64@0.25.11': resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.25.3': - resolution: {integrity: sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-x64@0.25.11': resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.3': - resolution: {integrity: sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openharmony-arm64@0.25.11': resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==, tarball: https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz} engines: {node: '>=18'} @@ -1117,48 +991,24 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.25.3': - resolution: {integrity: sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/win32-arm64@0.25.11': resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.25.3': - resolution: {integrity: sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-ia32@0.25.11': resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.25.3': - resolution: {integrity: sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-x64@0.25.11': resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.25.3': - resolution: {integrity: sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==, tarball: https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2511,9 +2361,6 @@ packages: '@types/body-parser@1.19.2': resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==, tarball: https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz} - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==, tarball: https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==, tarball: https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz} @@ -3156,8 +3003,8 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, tarball: https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz} - chai@5.2.1: - resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==, tarball: https://registry.npmjs.org/chai/-/chai-5.2.1.tgz} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==, tarball: https://registry.npmjs.org/chai/-/chai-5.3.3.tgz} engines: {node: '>=18'} chai@6.2.0: @@ -3687,11 +3534,6 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.25.3: - resolution: {integrity: sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==, tarball: https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz} - engines: {node: '>=18'} - hasBin: true - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, tarball: https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz} engines: {node: '>=6'} @@ -4671,8 +4513,8 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, tarball: https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz} hasBin: true - loupe@3.2.0: - resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==, tarball: https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==, tarball: https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz} lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==, tarball: https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz} @@ -5145,8 +4987,8 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, tarball: https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz} - pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==, tarball: https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==, tarball: https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz} engines: {node: '>= 14.16'} picocolors@1.1.1: @@ -5499,8 +5341,8 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==, tarball: https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz} engines: {node: '>= 14.18.0'} - recast@0.23.9: - resolution: {integrity: sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==, tarball: https://registry.npmjs.org/recast/-/recast-0.23.9.tgz} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==, tarball: https://registry.npmjs.org/recast/-/recast-0.23.11.tgz} engines: {node: '>= 4'} recharts-scale@0.4.5: @@ -5778,8 +5620,8 @@ packages: react-dom: optional: true - storybook@9.1.2: - resolution: {integrity: sha512-TYcq7WmgfVCAQge/KueGkVlM/+g33sQcmbATlC3X6y/g2FEeSSLGrb6E6d3iemht8oio+aY6ld3YOdAnMwx45Q==, tarball: https://registry.npmjs.org/storybook/-/storybook-9.1.2.tgz} + storybook@9.1.16: + resolution: {integrity: sha512-339U14K6l46EFyRvaPS2ZlL7v7Pb+LlcXT8KAETrGPxq8v1sAjj2HAOB6zrlAK3M+0+ricssfAwsLCwt7Eg8TQ==, tarball: https://registry.npmjs.org/storybook/-/storybook-9.1.16.tgz} hasBin: true peerDependencies: prettier: ^2 || ^3 @@ -5937,8 +5779,8 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==, tarball: https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz} engines: {node: '>=14.0.0'} - tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==, tarball: https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==, tarball: https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz} engines: {node: '>=14.0.0'} tldts-core@7.0.17: @@ -6443,30 +6285,6 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==, tarball: https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==, tarball: https://registry.npmjs.org/ws/-/ws-8.17.1.tgz} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==, tarball: https://registry.npmjs.org/ws/-/ws-8.18.0.tgz} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==, tarball: https://registry.npmjs.org/ws/-/ws-8.18.3.tgz} engines: {node: '>=10.0.0'} @@ -6840,13 +6658,13 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 - '@chromatic-com/storybook@4.1.0(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@chromatic-com/storybook@4.1.0(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: '@neoconfetti/react': 1.0.0 chromatic: 12.2.0 filesize: 10.1.2 jsonfile: 6.1.0 - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) strip-ansi: 7.1.0 transitivePeerDependencies: - '@chromatic-com/cypress' @@ -6998,156 +6816,81 @@ snapshots: '@esbuild/aix-ppc64@0.25.11': optional: true - '@esbuild/aix-ppc64@0.25.3': - optional: true - '@esbuild/android-arm64@0.25.11': optional: true - '@esbuild/android-arm64@0.25.3': - optional: true - '@esbuild/android-arm@0.25.11': optional: true - '@esbuild/android-arm@0.25.3': - optional: true - '@esbuild/android-x64@0.25.11': optional: true - '@esbuild/android-x64@0.25.3': - optional: true - '@esbuild/darwin-arm64@0.25.11': optional: true - '@esbuild/darwin-arm64@0.25.3': - optional: true - '@esbuild/darwin-x64@0.25.11': optional: true - '@esbuild/darwin-x64@0.25.3': - optional: true - '@esbuild/freebsd-arm64@0.25.11': optional: true - '@esbuild/freebsd-arm64@0.25.3': - optional: true - '@esbuild/freebsd-x64@0.25.11': optional: true - '@esbuild/freebsd-x64@0.25.3': - optional: true - '@esbuild/linux-arm64@0.25.11': optional: true - '@esbuild/linux-arm64@0.25.3': - optional: true - '@esbuild/linux-arm@0.25.11': optional: true - '@esbuild/linux-arm@0.25.3': - optional: true - '@esbuild/linux-ia32@0.25.11': optional: true - '@esbuild/linux-ia32@0.25.3': - optional: true - '@esbuild/linux-loong64@0.25.11': optional: true - '@esbuild/linux-loong64@0.25.3': - optional: true - '@esbuild/linux-mips64el@0.25.11': optional: true - '@esbuild/linux-mips64el@0.25.3': - optional: true - '@esbuild/linux-ppc64@0.25.11': optional: true - '@esbuild/linux-ppc64@0.25.3': - optional: true - '@esbuild/linux-riscv64@0.25.11': optional: true - '@esbuild/linux-riscv64@0.25.3': - optional: true - '@esbuild/linux-s390x@0.25.11': optional: true - '@esbuild/linux-s390x@0.25.3': - optional: true - '@esbuild/linux-x64@0.25.11': optional: true - '@esbuild/linux-x64@0.25.3': - optional: true - '@esbuild/netbsd-arm64@0.25.11': optional: true - '@esbuild/netbsd-arm64@0.25.3': - optional: true - '@esbuild/netbsd-x64@0.25.11': optional: true - '@esbuild/netbsd-x64@0.25.3': - optional: true - '@esbuild/openbsd-arm64@0.25.11': optional: true - '@esbuild/openbsd-arm64@0.25.3': - optional: true - '@esbuild/openbsd-x64@0.25.11': optional: true - '@esbuild/openbsd-x64@0.25.3': - optional: true - '@esbuild/openharmony-arm64@0.25.11': optional: true '@esbuild/sunos-x64@0.25.11': optional: true - '@esbuild/sunos-x64@0.25.3': - optional: true - '@esbuild/win32-arm64@0.25.11': optional: true - '@esbuild/win32-arm64@0.25.3': - optional: true - '@esbuild/win32-ia32@0.25.11': optional: true - '@esbuild/win32-ia32@0.25.3': - optional: true - '@esbuild/win32-x64@0.25.11': optional: true - '@esbuild/win32-x64@0.25.3': - optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@8.52.0)': dependencies: eslint: 8.52.0 @@ -8350,41 +8093,41 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@storybook/addon-docs@9.1.2(@types/react@19.1.17)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/addon-docs@9.1.2(@types/react@19.1.17)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: '@mdx-js/react': 3.0.1(@types/react@19.1.17)(react@19.1.1) - '@storybook/csf-plugin': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + '@storybook/csf-plugin': 9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/icons': 1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-links@9.1.2(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/addon-links@9.1.2(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: '@storybook/global': 5.0.0 - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: react: 19.1.1 - '@storybook/addon-themes@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/addon-themes@9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) ts-dedent: 2.2.0 - '@storybook/builder-vite@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + '@storybook/builder-vite@9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: - '@storybook/csf-plugin': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + '@storybook/csf-plugin': 9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) ts-dedent: 2.2.0 vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) - '@storybook/csf-plugin@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/csf-plugin@9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) unplugin: 1.5.0 '@storybook/global@5.0.0': {} @@ -8394,25 +8137,25 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - '@storybook/react-dom-shim@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/react-dom-shim@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) - '@storybook/react-vite@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + '@storybook/react-vite@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) '@rollup/pluginutils': 5.0.5(rollup@4.52.5) - '@storybook/builder-vite': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) - '@storybook/react': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3) + '@storybook/builder-vite': 9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + '@storybook/react': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3) find-up: 7.0.0 magic-string: 0.30.17 react: 19.1.1 react-docgen: 8.0.0 react-dom: 19.1.1(react@19.1.1) resolve: 1.22.10 - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) tsconfig-paths: 4.2.0 vite: 7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0) transitivePeerDependencies: @@ -8420,13 +8163,13 @@ snapshots: - supports-color - typescript - '@storybook/react@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)': + '@storybook/react@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: typescript: 5.6.3 @@ -8603,10 +8346,6 @@ snapshots: '@types/connect': 3.4.35 '@types/node': 20.17.16 - '@types/chai@5.2.2': - dependencies: - '@types/deep-eql': 4.0.2 - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -8886,10 +8625,10 @@ snapshots: '@vitest/expect@3.2.4': dependencies: - '@types/chai': 5.2.2 + '@types/chai': 5.2.3 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 - chai: 5.2.1 + chai: 5.3.3 tinyrainbow: 2.0.0 '@vitest/expect@4.0.6': @@ -8940,14 +8679,14 @@ snapshots: '@vitest/spy@3.2.4': dependencies: - tinyspy: 4.0.3 + tinyspy: 4.0.4 '@vitest/spy@4.0.6': {} '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 - loupe: 3.2.0 + loupe: 3.2.1 tinyrainbow: 2.0.0 '@vitest/utils@4.0.6': @@ -9308,13 +9047,13 @@ snapshots: ccount@2.0.1: {} - chai@5.2.1: + chai@5.3.3: dependencies: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.2.0 - pathval: 2.0.0 + loupe: 3.2.1 + pathval: 2.0.1 chai@6.2.0: {} @@ -9772,10 +9511,10 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild-register@3.6.0(esbuild@0.25.3): + esbuild-register@3.6.0(esbuild@0.25.11): dependencies: debug: 4.4.3 - esbuild: 0.25.3 + esbuild: 0.25.11 transitivePeerDependencies: - supports-color @@ -9808,34 +9547,6 @@ snapshots: '@esbuild/win32-ia32': 0.25.11 '@esbuild/win32-x64': 0.25.11 - esbuild@0.25.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.3 - '@esbuild/android-arm': 0.25.3 - '@esbuild/android-arm64': 0.25.3 - '@esbuild/android-x64': 0.25.3 - '@esbuild/darwin-arm64': 0.25.3 - '@esbuild/darwin-x64': 0.25.3 - '@esbuild/freebsd-arm64': 0.25.3 - '@esbuild/freebsd-x64': 0.25.3 - '@esbuild/linux-arm': 0.25.3 - '@esbuild/linux-arm64': 0.25.3 - '@esbuild/linux-ia32': 0.25.3 - '@esbuild/linux-loong64': 0.25.3 - '@esbuild/linux-mips64el': 0.25.3 - '@esbuild/linux-ppc64': 0.25.3 - '@esbuild/linux-riscv64': 0.25.3 - '@esbuild/linux-s390x': 0.25.3 - '@esbuild/linux-x64': 0.25.3 - '@esbuild/netbsd-arm64': 0.25.3 - '@esbuild/netbsd-x64': 0.25.3 - '@esbuild/openbsd-arm64': 0.25.3 - '@esbuild/openbsd-x64': 0.25.3 - '@esbuild/sunos-x64': 0.25.3 - '@esbuild/win32-arm64': 0.25.3 - '@esbuild/win32-ia32': 0.25.3 - '@esbuild/win32-x64': 0.25.3 - escalade@3.2.0: {} escape-html@1.0.3: {} @@ -11021,7 +10732,7 @@ snapshots: whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 - ws: 8.17.1 + ws: 8.18.3 xml-name-validator: 4.0.0 transitivePeerDependencies: - bufferutil @@ -11161,7 +10872,7 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@3.2.0: {} + loupe@3.2.1: {} lowlight@1.20.0: dependencies: @@ -11850,7 +11561,7 @@ snapshots: pathe@2.0.3: {} - pathval@2.0.0: {} + pathval@2.0.1: {} picocolors@1.1.1: {} @@ -12218,7 +11929,7 @@ snapshots: readdirp@4.1.2: {} - recast@0.23.9: + recast@0.23.11: dependencies: ast-types: 0.16.1 esprima: 4.0.1 @@ -12534,18 +12245,18 @@ snapshots: dependencies: internal-slot: 1.0.6 - storybook-addon-remix-react-router@5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))): + storybook-addon-remix-react-router@5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))): dependencies: '@mjackson/form-data-parser': 0.4.0 compare-versions: 6.1.0 react-inspector: 6.0.2(react@19.1.1) react-router: 7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - storybook: 9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook@9.1.2(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)): + storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)): dependencies: '@storybook/global': 5.0.0 '@testing-library/jest-dom': 6.9.1 @@ -12554,11 +12265,11 @@ snapshots: '@vitest/mocker': 3.2.4(msw@2.4.8(typescript@5.6.3))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) '@vitest/spy': 3.2.4 better-opn: 3.0.2 - esbuild: 0.25.3 - esbuild-register: 3.6.0(esbuild@0.25.3) - recast: 0.23.9 + esbuild: 0.25.11 + esbuild-register: 3.6.0(esbuild@0.25.11) + recast: 0.23.11 semver: 7.7.3 - ws: 8.18.0 + ws: 8.18.3 optionalDependencies: prettier: 3.4.1 transitivePeerDependencies: @@ -12735,7 +12446,7 @@ snapshots: tinyrainbow@3.0.3: {} - tinyspy@4.0.3: {} + tinyspy@4.0.4: {} tldts-core@7.0.17: {} @@ -13203,10 +12914,6 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 - ws@8.17.1: {} - - ws@8.18.0: {} - ws@8.18.3: {} xml-name-validator@4.0.0: {} From cb5ddec5c5859b26e242476d0f9b1ab9acddc028 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:45:58 +0000 Subject: [PATCH 068/255] chore: bump monaco-editor from 0.53.0 to 0.54.0 in /site (#20626) Bumps [monaco-editor](https://github.com/microsoft/monaco-editor) from 0.53.0 to 0.54.0.
Release notes

Sourced from monaco-editor's releases.

v0.54.0

Changes:

  • #4986: sets correct node version
  • #4977: update samples

This list of changes was auto generated.

v0.54.0-dev-20251006

No release notes provided.

v0.54.0-dev-20251005

No release notes provided.

v0.54.0-dev-20251004

No release notes provided.

v0.54.0-dev-20251003

No release notes provided.

v0.54.0-dev-20251002

No release notes provided.

v0.54.0-dev-20251001

No release notes provided.

v0.54.0-dev-20250930

No release notes provided.

v0.54.0-dev-20250929

No release notes provided.

v0.54.0-dev-20250928

Changes:

... (truncated)

Changelog

Sourced from monaco-editor's changelog.

[0.54.0]

  • Adds option editor.mouseMiddleClickAction
  • Various bug fixes
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=monaco-editor&package-manager=npm_and_yarn&previous-version=0.53.0&new-version=0.54.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/site/package.json b/site/package.json index 49b1baee05ab4..a78374819b318 100644 --- a/site/package.json +++ b/site/package.json @@ -91,7 +91,7 @@ "jszip": "3.10.1", "lodash": "4.17.21", "lucide-react": "0.552.0", - "monaco-editor": "0.53.0", + "monaco-editor": "0.54.0", "pretty-bytes": "6.1.1", "react": "19.1.1", "react-color": "2.19.3", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 49b36a8b1e753..04993eb197d9a 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -54,7 +54,7 @@ importers: version: 5.2.7 '@monaco-editor/react': specifier: 4.7.0 - version: 4.7.0(monaco-editor@0.53.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 4.7.0(monaco-editor@0.54.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@mui/material': specifier: 5.18.0 version: 5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -188,8 +188,8 @@ importers: specifier: 0.552.0 version: 0.552.0(react@19.1.1) monaco-editor: - specifier: 0.53.0 - version: 0.53.0 + specifier: 0.54.0 + version: 0.54.0 pretty-bytes: specifier: 6.1.1 version: 6.1.1 @@ -2588,8 +2588,8 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==, tarball: https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz} - '@types/trusted-types@1.0.6': - resolution: {integrity: sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==, tarball: https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==, tarball: https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz} '@types/ua-parser-js@0.7.36': resolution: {integrity: sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==, tarball: https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz} @@ -3444,6 +3444,9 @@ packages: engines: {node: '>=12'} deprecated: Use your platform's native DOMException instead + dompurify@3.2.6: + resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==, tarball: https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz} + dpdm@3.14.0: resolution: {integrity: sha512-YJzsFSyEtj88q5eTELg3UWU7TVZkG1dpbF4JDQ3t1b07xuzXmdoGeSz9TKOke1mUuOpWlk4q+pBh+aHzD6GBTg==, tarball: https://registry.npmjs.org/dpdm/-/dpdm-3.14.0.tgz} hasBin: true @@ -4561,6 +4564,11 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==, tarball: https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz} + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==, tarball: https://registry.npmjs.org/marked/-/marked-14.0.0.tgz} + engines: {node: '>= 18'} + hasBin: true + material-colors@1.2.6: resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==, tarball: https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz} @@ -4764,8 +4772,8 @@ packages: resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==, tarball: https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz} engines: {node: '>= 8'} - monaco-editor@0.53.0: - resolution: {integrity: sha512-0WNThgC6CMWNXXBxTbaYYcunj08iB5rnx4/G56UOPeL9UVIUGGHA1GR0EWIh9Ebabj7NpCRawQ5b0hfN1jQmYQ==, tarball: https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.53.0.tgz} + monaco-editor@0.54.0: + resolution: {integrity: sha512-hx45SEUoLatgWxHKCmlLJH81xBo0uXP4sRkESUpmDQevfi+e7K1VuiSprK6UpQ8u4zOcKNiH0pMvHvlMWA/4cw==, tarball: https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.54.0.tgz} moo-color@1.0.3: resolution: {integrity: sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==, tarball: https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz} @@ -7269,10 +7277,10 @@ snapshots: dependencies: state-local: 1.0.7 - '@monaco-editor/react@4.7.0(monaco-editor@0.53.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@monaco-editor/react@4.7.0(monaco-editor@0.54.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@monaco-editor/loader': 1.5.0 - monaco-editor: 0.53.0 + monaco-editor: 0.54.0 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -8585,7 +8593,8 @@ snapshots: '@types/tough-cookie@4.0.5': {} - '@types/trusted-types@1.0.6': {} + '@types/trusted-types@2.0.7': + optional: true '@types/ua-parser-js@0.7.36': {} @@ -9432,6 +9441,10 @@ snapshots: dependencies: webidl-conversions: 7.0.0 + dompurify@3.2.6: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dpdm@3.14.0: dependencies: chalk: 4.1.2 @@ -10916,6 +10929,8 @@ snapshots: markdown-table@3.0.4: {} + marked@14.0.0: {} + material-colors@1.2.6: {} math-intrinsics@1.1.0: {} @@ -11309,9 +11324,10 @@ snapshots: mock-socket@9.3.1: {} - monaco-editor@0.53.0: + monaco-editor@0.54.0: dependencies: - '@types/trusted-types': 1.0.6 + dompurify: 3.2.6 + marked: 14.0.0 moo-color@1.0.3: dependencies: From dc5b877f26130e3fa3394052233d46908f938ebe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:56:11 +0000 Subject: [PATCH 069/255] chore: bump the react group across 1 directory with 4 updates (#20615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the react group with 4 updates in the /site directory: [react](https://github.com/facebook/react/tree/HEAD/packages/react), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react), [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). Updates `react` from 19.1.1 to 19.2.0
Release notes

Sourced from react's releases.

19.2.0 (Oct 1, 2025)

Below is a list of all new features, APIs, and bug fixes.

Read the React 19.2 release post for more information.

New React Features

  • <Activity>: A new API to hide and restore the UI and internal state of its children.
  • useEffectEvent is a React Hook that lets you extract non-reactive logic into an Effect Event.
  • cacheSignal (for RSCs) lets your know when the cache() lifetime is over.
  • React Performance tracks appear on the Performance panel’s timeline in your browser developer tools

New React DOM Features

  • Added resume APIs for partial pre-rendering with Web Streams:
  • Added resume APIs for partial pre-rendering with Node Streams:
  • Updated prerender APIs to return a postponed state that can be passed to the resume APIs.

Notable changes

  • React DOM now batches suspense boundary reveals, matching the behavior of client side rendering. This change is especially noticeable when animating the reveal of Suspense boundaries e.g. with the upcoming <ViewTransition> Component. React will batch as much reveals as possible before the first paint while trying to hit popular first-contentful paint metrics.
  • Add Node Web Streams (prerender, renderToReadableStream) to server-side-rendering APIs for Node.js
  • Use underscore instead of : IDs generated by useId

All Changes

React

React DOM

... (truncated)

Changelog

Sourced from react's changelog.

19.2.0 (October 1st, 2025)

Below is a list of all new features, APIs, and bug fixes.

Read the React 19.2 release post for more information.

New React Features

  • <Activity>: A new API to hide and restore the UI and internal state of its children.
  • useEffectEvent is a React Hook that lets you extract non-reactive logic into an Effect Event.
  • cacheSignal (for RSCs) lets your know when the cache() lifetime is over.
  • React Performance tracks appear on the Performance panel’s timeline in your browser developer tools

New React DOM Features

  • Added resume APIs for partial pre-rendering with Web Streams:
  • Added resume APIs for partial pre-rendering with Node Streams:
  • Updated prerender APIs to return a postponed state that can be passed to the resume APIs.

Notable changes

  • React DOM now batches suspense boundary reveals, matching the behavior of client side rendering. This change is especially noticeable when animating the reveal of Suspense boundaries e.g. with the upcoming <ViewTransition> Component. React will batch as much reveals as possible before the first paint while trying to hit popular first-contentful paint metrics.
  • Add Node Web Streams (prerender, renderToReadableStream) to server-side-rendering APIs for Node.js
  • Use underscore instead of : IDs generated by useId

All Changes

React

React DOM

... (truncated)

Commits

Updates `@types/react` from 19.1.17 to 19.2.2
Commits

Updates `react-dom` from 19.1.1 to 19.2.0
Release notes

Sourced from react-dom's releases.

19.2.0 (Oct 1, 2025)

Below is a list of all new features, APIs, and bug fixes.

Read the React 19.2 release post for more information.

New React Features

  • <Activity>: A new API to hide and restore the UI and internal state of its children.
  • useEffectEvent is a React Hook that lets you extract non-reactive logic into an Effect Event.
  • cacheSignal (for RSCs) lets your know when the cache() lifetime is over.
  • React Performance tracks appear on the Performance panel’s timeline in your browser developer tools

New React DOM Features

  • Added resume APIs for partial pre-rendering with Web Streams:
  • Added resume APIs for partial pre-rendering with Node Streams:
  • Updated prerender APIs to return a postponed state that can be passed to the resume APIs.

Notable changes

  • React DOM now batches suspense boundary reveals, matching the behavior of client side rendering. This change is especially noticeable when animating the reveal of Suspense boundaries e.g. with the upcoming <ViewTransition> Component. React will batch as much reveals as possible before the first paint while trying to hit popular first-contentful paint metrics.
  • Add Node Web Streams (prerender, renderToReadableStream) to server-side-rendering APIs for Node.js
  • Use underscore instead of : IDs generated by useId

All Changes

React

React DOM

... (truncated)

Changelog

Sourced from react-dom's changelog.

19.2.0 (October 1st, 2025)

Below is a list of all new features, APIs, and bug fixes.

Read the React 19.2 release post for more information.

New React Features

  • <Activity>: A new API to hide and restore the UI and internal state of its children.
  • useEffectEvent is a React Hook that lets you extract non-reactive logic into an Effect Event.
  • cacheSignal (for RSCs) lets your know when the cache() lifetime is over.
  • React Performance tracks appear on the Performance panel’s timeline in your browser developer tools

New React DOM Features

  • Added resume APIs for partial pre-rendering with Web Streams:
  • Added resume APIs for partial pre-rendering with Node Streams:
  • Updated prerender APIs to return a postponed state that can be passed to the resume APIs.

Notable changes

  • React DOM now batches suspense boundary reveals, matching the behavior of client side rendering. This change is especially noticeable when animating the reveal of Suspense boundaries e.g. with the upcoming <ViewTransition> Component. React will batch as much reveals as possible before the first paint while trying to hit popular first-contentful paint metrics.
  • Add Node Web Streams (prerender, renderToReadableStream) to server-side-rendering APIs for Node.js
  • Use underscore instead of : IDs generated by useId

All Changes

React

React DOM

... (truncated)

Commits

Updates `@types/react-dom` from 19.1.11 to 19.2.2
Commits

Updates `@types/react` from 19.1.17 to 19.2.2
Commits

Updates `@types/react-dom` from 19.1.11 to 19.2.2
Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 8 +- site/pnpm-lock.yaml | 1264 +++++++++++++++++++++---------------------- 2 files changed, 636 insertions(+), 636 deletions(-) diff --git a/site/package.json b/site/package.json index a78374819b318..cb671274f8181 100644 --- a/site/package.json +++ b/site/package.json @@ -93,11 +93,11 @@ "lucide-react": "0.552.0", "monaco-editor": "0.54.0", "pretty-bytes": "6.1.1", - "react": "19.1.1", + "react": "19.2.0", "react-color": "2.19.3", "react-confetti": "6.4.0", "react-date-range": "1.4.0", - "react-dom": "19.1.1", + "react-dom": "19.2.0", "react-markdown": "9.1.0", "react-query": "npm:@tanstack/react-query@5.77.0", "react-resizable-panels": "3.0.6", @@ -144,10 +144,10 @@ "@types/jest": "29.5.14", "@types/lodash": "4.17.20", "@types/node": "20.17.16", - "@types/react": "19.1.17", + "@types/react": "19.2.2", "@types/react-color": "3.0.13", "@types/react-date-range": "1.4.4", - "@types/react-dom": "19.1.11", + "@types/react-dom": "19.2.2", "@types/react-syntax-highlighter": "15.5.13", "@types/react-virtualized-auto-sizer": "1.0.8", "@types/react-window": "1.8.8", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 04993eb197d9a..c953784a890f1 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -24,7 +24,7 @@ importers: version: 1.2.1 '@emoji-mart/react': specifier: 1.1.1 - version: 1.1.1(emoji-mart@5.6.0)(react@19.1.1) + version: 1.1.1(emoji-mart@5.6.0)(react@19.2.0) '@emotion/cache': specifier: 11.14.0 version: 11.14.0 @@ -33,10 +33,10 @@ importers: version: 11.13.5 '@emotion/react': specifier: 11.14.0 - version: 11.14.0(@types/react@19.1.17)(react@19.1.1) + version: 11.14.0(@types/react@19.2.2)(react@19.2.0) '@emotion/styled': specifier: 11.14.1 - version: 11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1) + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) '@fontsource-variable/inter': specifier: 5.1.1 version: 5.1.1 @@ -54,67 +54,67 @@ importers: version: 5.2.7 '@monaco-editor/react': specifier: 4.7.0 - version: 4.7.0(monaco-editor@0.54.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 4.7.0(monaco-editor@0.54.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@mui/material': specifier: 5.18.0 - version: 5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@mui/system': specifier: 5.18.0 - version: 5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1) + version: 5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) '@mui/utils': specifier: 5.17.1 - version: 5.17.1(@types/react@19.1.17)(react@19.1.1) + version: 5.17.1(@types/react@19.2.2)(react@19.2.0) '@mui/x-tree-view': specifier: 7.29.10 - version: 7.29.10(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.29.10(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-avatar': specifier: 1.1.10 - version: 1.1.10(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-checkbox': specifier: 1.3.3 - version: 1.3.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-collapsible': specifier: 1.1.12 - version: 1.1.12(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-dialog': specifier: 1.1.15 - version: 1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-dropdown-menu': specifier: 2.1.16 - version: 2.1.16(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-label': specifier: 2.1.7 - version: 2.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-popover': specifier: 1.1.15 - version: 1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-radio-group': specifier: 1.3.8 - version: 1.3.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.3.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-scroll-area': specifier: 1.2.10 - version: 1.2.10(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-select': specifier: 2.2.6 - version: 2.2.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-separator': specifier: 1.1.7 - version: 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-slider': specifier: 1.3.6 - version: 1.3.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.3.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-slot': specifier: 1.2.3 - version: 1.2.3(@types/react@19.1.17)(react@19.1.1) + version: 1.2.3(@types/react@19.2.2)(react@19.2.0) '@radix-ui/react-switch': specifier: 1.2.6 - version: 1.2.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-tooltip': specifier: 1.2.8 - version: 1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tanstack/react-query-devtools': specifier: 5.77.0 - version: 5.77.0(@tanstack/react-query@5.77.0(react@19.1.1))(react@19.1.1) + version: 5.77.0(@tanstack/react-query@5.77.0(react@19.2.0))(react@19.2.0) '@xterm/addon-canvas': specifier: 0.7.0 version: 0.7.0(@xterm/xterm@5.5.0) @@ -150,7 +150,7 @@ importers: version: 2.1.1 cmdk: specifier: 1.1.1 - version: 1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) color-convert: specifier: 2.0.1 version: 2.0.1 @@ -171,7 +171,7 @@ importers: version: 2.0.5 formik: specifier: 2.4.6 - version: 2.4.6(react@19.1.1) + version: 2.4.6(react@19.2.0) front-matter: specifier: 4.0.2 version: 4.0.2 @@ -186,7 +186,7 @@ importers: version: 4.17.21 lucide-react: specifier: 0.552.0 - version: 0.552.0(react@19.1.1) + version: 0.552.0(react@19.2.0) monaco-editor: specifier: 0.54.0 version: 0.54.0 @@ -194,47 +194,47 @@ importers: specifier: 6.1.1 version: 6.1.1 react: - specifier: 19.1.1 - version: 19.1.1 + specifier: 19.2.0 + version: 19.2.0 react-color: specifier: 2.19.3 - version: 2.19.3(react@19.1.1) + version: 2.19.3(react@19.2.0) react-confetti: specifier: 6.4.0 - version: 6.4.0(react@19.1.1) + version: 6.4.0(react@19.2.0) react-date-range: specifier: 1.4.0 - version: 1.4.0(date-fns@2.30.0)(react@19.1.1) + version: 1.4.0(date-fns@2.30.0)(react@19.2.0) react-dom: - specifier: 19.1.1 - version: 19.1.1(react@19.1.1) + specifier: 19.2.0 + version: 19.2.0(react@19.2.0) react-markdown: specifier: 9.1.0 - version: 9.1.0(@types/react@19.1.17)(react@19.1.1) + version: 9.1.0(@types/react@19.2.2)(react@19.2.0) react-query: specifier: npm:@tanstack/react-query@5.77.0 - version: '@tanstack/react-query@5.77.0(react@19.1.1)' + version: '@tanstack/react-query@5.77.0(react@19.2.0)' react-resizable-panels: specifier: 3.0.6 - version: 3.0.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 3.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-router: specifier: 7.9.5 - version: 7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-syntax-highlighter: specifier: 15.6.1 - version: 15.6.1(react@19.1.1) + version: 15.6.1(react@19.2.0) react-textarea-autosize: specifier: 8.5.9 - version: 8.5.9(@types/react@19.1.17)(react@19.1.1) + version: 8.5.9(@types/react@19.2.2)(react@19.2.0) react-virtualized-auto-sizer: specifier: 1.0.26 - version: 1.0.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.0.26(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-window: specifier: 1.8.11 - version: 1.8.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.8.11(react-dom@19.2.0(react@19.2.0))(react@19.2.0) recharts: specifier: 2.15.4 - version: 2.15.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 2.15.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) remark-gfm: specifier: 4.0.1 version: 4.0.1 @@ -289,16 +289,16 @@ importers: version: 1.50.1 '@storybook/addon-docs': specifier: 9.1.2 - version: 9.1.2(@types/react@19.1.17)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 9.1.2(@types/react@19.2.2)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/addon-links': specifier: 9.1.2 - version: 9.1.2(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 9.1.2(react@19.2.0)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/addon-themes': specifier: 9.1.2 version: 9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) '@storybook/react-vite': specifier: 9.1.2 - version: 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) + version: 9.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.52.5)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) '@swc/core': specifier: 1.3.38 version: 1.3.38 @@ -313,7 +313,7 @@ importers: version: 6.9.1 '@testing-library/react': specifier: 14.3.1 - version: 14.3.1(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 14.3.1(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@testing-library/user-event': specifier: 14.6.1 version: 14.6.1(@testing-library/dom@10.4.0) @@ -342,23 +342,23 @@ importers: specifier: 20.17.16 version: 20.17.16 '@types/react': - specifier: 19.1.17 - version: 19.1.17 + specifier: 19.2.2 + version: 19.2.2 '@types/react-color': specifier: 3.0.13 - version: 3.0.13(@types/react@19.1.17) + version: 3.0.13(@types/react@19.2.2) '@types/react-date-range': specifier: 1.4.4 version: 1.4.4 '@types/react-dom': - specifier: 19.1.11 - version: 19.1.11(@types/react@19.1.17) + specifier: 19.2.2 + version: 19.2.2(@types/react@19.2.2) '@types/react-syntax-highlighter': specifier: 15.5.13 version: 15.5.13 '@types/react-virtualized-auto-sizer': specifier: 1.0.8 - version: 1.0.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.0.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@types/react-window': specifier: 1.8.8 version: 1.8.8 @@ -439,7 +439,7 @@ importers: version: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) storybook-addon-remix-react-router: specifier: 5.0.0 - version: 5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + version: 5.0.0(react-dom@19.2.0(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) tailwindcss: specifier: 3.4.18 version: 3.4.18(yaml@2.7.0) @@ -2530,10 +2530,10 @@ packages: peerDependencies: '@types/react': ^18.0.0 - '@types/react-dom@19.1.11': - resolution: {integrity: sha512-3BKc/yGdNTYQVVw4idqHtSOcFsgGuBbMveKCOgF8wQ5QtrYOc3jDIlzg3jef04zcXFIHLelyGlj0T+BJ8+KN+w==, tarball: https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.11.tgz} + '@types/react-dom@19.2.2': + resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==, tarball: https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz} peerDependencies: - '@types/react': ^19.0.0 + '@types/react': ^19.2.0 '@types/react-syntax-highlighter@15.5.13': resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==, tarball: https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz} @@ -2550,8 +2550,8 @@ packages: '@types/react-window@1.8.8': resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==, tarball: https://registry.npmjs.org/@types/react-window/-/react-window-1.8.8.tgz} - '@types/react@19.1.17': - resolution: {integrity: sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==, tarball: https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz} + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==, tarball: https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz} '@types/reactcss@1.2.13': resolution: {integrity: sha512-gi3S+aUi6kpkF5vdhUsnkwbiSEIU/BEJyD7kBy2SudWBUuKmJk8AQKE0OVcQQeEy40Azh0lV6uynxlikYIJuwg==, tarball: https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.13.tgz} @@ -5200,10 +5200,10 @@ packages: resolution: {integrity: sha512-kmob/FOTwep7DUWf9KjuenKX0vyvChr3oTdvvPt09V60Iz75FJp+T/0ZeHMbAfJj2WaVWqAPP5Hmm3PYzSPPKg==, tarball: https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.0.tgz} engines: {node: ^20.9.0 || >=22} - react-dom@19.1.1: - resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==, tarball: https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz} + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==, tarball: https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz} peerDependencies: - react: ^19.1.1 + react: ^19.2.0 react-fast-compare@2.0.4: resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==, tarball: https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz} @@ -5322,8 +5322,8 @@ packages: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react@19.1.1: - resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==, tarball: https://registry.npmjs.org/react/-/react-19.1.1.tgz} + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==, tarball: https://registry.npmjs.org/react/-/react-19.2.0.tgz} engines: {node: '>=0.10.0'} reactcss@1.2.3: @@ -5477,8 +5477,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==, tarball: https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz} engines: {node: '>=v12.22.7'} - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==, tarball: https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==, tarball: https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz} semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==, tarball: https://registry.npmjs.org/semver/-/semver-7.7.3.tgz} @@ -6723,10 +6723,10 @@ snapshots: '@emoji-mart/data@1.2.1': {} - '@emoji-mart/react@1.1.1(emoji-mart@5.6.0)(react@19.1.1)': + '@emoji-mart/react@1.1.1(emoji-mart@5.6.0)(react@19.2.0)': dependencies: emoji-mart: 5.6.0 - react: 19.1.1 + react: 19.2.0 '@emotion/babel-plugin@11.13.5': dependencies: @@ -6770,19 +6770,19 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1)': + '@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0) '@emotion/utils': 1.4.2 '@emotion/weak-memoize': 0.4.0 hoist-non-react-statics: 3.3.2 - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 transitivePeerDependencies: - supports-color @@ -6796,26 +6796,26 @@ snapshots: '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1)': + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@19.1.17)(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0) '@emotion/utils': 1.4.2 - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 transitivePeerDependencies: - supports-color '@emotion/unitless@0.10.0': {} - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.1)': + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 '@emotion/utils@1.4.2': {} @@ -6935,11 +6935,11 @@ snapshots: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@floating-ui/react-dom@2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@floating-ui/dom': 1.7.4 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) '@floating-ui/utils@0.2.10': {} @@ -6968,9 +6968,9 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': optional: true - '@icons/material@0.2.4(react@19.1.1)': + '@icons/material@0.2.4(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 '@inquirer/confirm@3.2.0': dependencies: @@ -7257,11 +7257,11 @@ snapshots: '@leeoniya/ufuzzy@1.0.10': {} - '@mdx-js/react@3.0.1(@types/react@19.1.17)(react@19.1.1)': + '@mdx-js/react@3.0.1(@types/react@19.2.2)(react@19.2.0)': dependencies: '@types/mdx': 2.0.9 - '@types/react': 19.1.17 - react: 19.1.1 + '@types/react': 19.2.2 + react: 19.2.0 '@mjackson/form-data-parser@0.4.0': dependencies: @@ -7277,12 +7277,12 @@ snapshots: dependencies: state-local: 1.0.7 - '@monaco-editor/react@4.7.0(monaco-editor@0.54.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@monaco-editor/react@4.7.0(monaco-editor@0.54.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@monaco-editor/loader': 1.5.0 monaco-editor: 0.54.0 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) '@mswjs/interceptors@0.35.9': dependencies: @@ -7295,104 +7295,104 @@ snapshots: '@mui/core-downloads-tracker@5.18.0': {} - '@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 '@mui/core-downloads-tracker': 5.18.0 - '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1) - '@mui/types': 7.2.24(@types/react@19.1.17) - '@mui/utils': 5.17.1(@types/react@19.1.17)(react@19.1.1) + '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@mui/types': 7.2.24(@types/react@19.2.2) + '@mui/utils': 5.17.1(@types/react@19.2.2)(react@19.2.0) '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@19.1.17) + '@types/react-transition-group': 4.4.12(@types/react@19.2.2) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) react-is: 19.1.1 - react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.17)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1) - '@types/react': 19.1.17 + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@types/react': 19.2.2 - '@mui/private-theming@5.17.1(@types/react@19.1.17)(react@19.1.1)': + '@mui/private-theming@5.17.1(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 - '@mui/utils': 5.17.1(@types/react@19.1.17)(react@19.1.1) + '@mui/utils': 5.17.1(@types/react@19.2.2)(react@19.2.0) prop-types: 15.8.1 - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@mui/styled-engine@5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1)': + '@mui/styled-engine@5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 csstype: 3.1.3 prop-types: 15.8.1 - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.17)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) - '@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1)': + '@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 - '@mui/private-theming': 5.17.1(@types/react@19.1.17)(react@19.1.1) - '@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(react@19.1.1) - '@mui/types': 7.2.24(@types/react@19.1.17) - '@mui/utils': 5.17.1(@types/react@19.1.17)(react@19.1.1) + '@mui/private-theming': 5.17.1(@types/react@19.2.2)(react@19.2.0) + '@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) + '@mui/types': 7.2.24(@types/react@19.2.2) + '@mui/utils': 5.17.1(@types/react@19.2.2)(react@19.2.0) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.17)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1) - '@types/react': 19.1.17 + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@types/react': 19.2.2 - '@mui/types@7.2.24(@types/react@19.1.17)': + '@mui/types@7.2.24(@types/react@19.2.2)': optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@mui/utils@5.17.1(@types/react@19.1.17)(react@19.1.1)': + '@mui/utils@5.17.1(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 - '@mui/types': 7.2.24(@types/react@19.1.17) + '@mui/types': 7.2.24(@types/react@19.2.2) '@types/prop-types': 15.7.15 clsx: 2.1.1 prop-types: 15.8.1 - react: 19.1.1 + react: 19.2.0 react-is: 19.1.1 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@mui/x-internals@7.29.0(@types/react@19.1.17)(react@19.1.1)': + '@mui/x-internals@7.29.0(@types/react@19.2.2)(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 - '@mui/utils': 5.17.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 + '@mui/utils': 5.17.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 transitivePeerDependencies: - '@types/react' - '@mui/x-tree-view@7.29.10(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@mui/x-tree-view@7.29.10(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 - '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1) - '@mui/utils': 5.17.1(@types/react@19.1.17)(react@19.1.1) - '@mui/x-internals': 7.29.0(@types/react@19.1.17)(react@19.1.1) - '@types/react-transition-group': 4.4.12(@types/react@19.1.17) + '@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) + '@mui/utils': 5.17.1(@types/react@19.2.2)(react@19.2.0) + '@mui/x-internals': 7.29.0(@types/react@19.2.2)(react@19.2.0) + '@types/react-transition-group': 4.4.12(@types/react@19.2.2) clsx: 2.1.1 prop-types: 15.8.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.17)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.17)(react@19.1.1))(@types/react@19.1.17)(react@19.1.1) + '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) transitivePeerDependencies: - '@types/react' @@ -7527,489 +7527,489 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-context@1.1.2(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) aria-hidden: 1.2.6 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.7.1(@types/react@19.1.17)(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-direction@1.1.1(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-id@1.1.1(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) aria-hidden: 1.2.6 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.7.1(@types/react@19.1.17)(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-popover@1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) aria-hidden: 1.2.6 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.7.1(@types/react@19.1.17)(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) - - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.17)(react@19.1.1) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) '@radix-ui/rect': 1.1.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) aria-hidden: 1.2.6 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.7.1(@types/react@19.1.17)(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-slider@1.3.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-slot@1.2.3(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 - use-sync-external-store: 1.6.0(react@19.1.1) + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: '@radix-ui/rect': 1.1.1 - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-use-size@1.1.1(@types/react@19.1.17)(react@19.1.1)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.17)(react@19.1.1) - react: 19.1.1 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 - '@types/react-dom': 19.1.11(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) '@radix-ui/rect@1.1.1': {} @@ -8101,25 +8101,25 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@storybook/addon-docs@9.1.2(@types/react@19.1.17)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/addon-docs@9.1.2(@types/react@19.2.2)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: - '@mdx-js/react': 3.0.1(@types/react@19.1.17)(react@19.1.1) + '@mdx-js/react': 3.0.1(@types/react@19.2.2)(react@19.2.0) '@storybook/csf-plugin': 9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) - '@storybook/icons': 1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@storybook/icons': 1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@storybook/react-dom-shim': 9.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-links@9.1.2(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/addon-links@9.1.2(react@19.2.0)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: '@storybook/global': 5.0.0 storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: - react: 19.1.1 + react: 19.2.0 '@storybook/addon-themes@9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: @@ -8140,28 +8140,28 @@ snapshots: '@storybook/global@5.0.0': {} - '@storybook/icons@1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@storybook/icons@1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - '@storybook/react-dom-shim@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': + '@storybook/react-dom-shim@9.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))': dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) - '@storybook/react-vite@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.52.5)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': + '@storybook/react-vite@9.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(rollup@4.52.5)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.6.3)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) '@rollup/pluginutils': 5.0.5(rollup@4.52.5) '@storybook/builder-vite': 9.1.2(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) - '@storybook/react': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3) + '@storybook/react': 9.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3) find-up: 7.0.0 magic-string: 0.30.17 - react: 19.1.1 + react: 19.2.0 react-docgen: 8.0.0 - react-dom: 19.1.1(react@19.1.1) + react-dom: 19.2.0(react@19.2.0) resolve: 1.22.10 storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) tsconfig-paths: 4.2.0 @@ -8171,12 +8171,12 @@ snapshots: - supports-color - typescript - '@storybook/react@9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)': + '@storybook/react@9.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)))(typescript@5.6.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 9.1.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@storybook/react-dom-shim': 9.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: typescript: 5.6.3 @@ -8245,16 +8245,16 @@ snapshots: '@tanstack/query-devtools@5.76.0': {} - '@tanstack/react-query-devtools@5.77.0(@tanstack/react-query@5.77.0(react@19.1.1))(react@19.1.1)': + '@tanstack/react-query-devtools@5.77.0(@tanstack/react-query@5.77.0(react@19.2.0))(react@19.2.0)': dependencies: '@tanstack/query-devtools': 5.76.0 - '@tanstack/react-query': 5.77.0(react@19.1.1) - react: 19.1.1 + '@tanstack/react-query': 5.77.0(react@19.2.0) + react: 19.2.0 - '@tanstack/react-query@5.77.0(react@19.1.1)': + '@tanstack/react-query@5.77.0(react@19.2.0)': dependencies: '@tanstack/query-core': 5.77.0 - react: 19.1.1 + react: 19.2.0 '@testing-library/dom@10.4.0': dependencies: @@ -8287,13 +8287,13 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@14.3.1(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@testing-library/react@14.3.1(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.26.10 '@testing-library/dom': 9.3.3 - '@types/react-dom': 18.3.7(@types/react@19.1.17) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@types/react-dom': 18.3.7(@types/react@19.2.2) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: - '@types/react' @@ -8443,7 +8443,7 @@ snapshots: '@types/hoist-non-react-statics@3.3.5': dependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 hoist-non-react-statics: 3.3.2 '@types/http-errors@2.0.1': {} @@ -8519,50 +8519,50 @@ snapshots: '@types/range-parser@1.2.4': {} - '@types/react-color@3.0.13(@types/react@19.1.17)': + '@types/react-color@3.0.13(@types/react@19.2.2)': dependencies: - '@types/react': 19.1.17 - '@types/reactcss': 1.2.13(@types/react@19.1.17) + '@types/react': 19.2.2 + '@types/reactcss': 1.2.13(@types/react@19.2.2) '@types/react-date-range@1.4.4': dependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 date-fns: 2.30.0 - '@types/react-dom@18.3.7(@types/react@19.1.17)': + '@types/react-dom@18.3.7(@types/react@19.2.2)': dependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@types/react-dom@19.1.11(@types/react@19.1.17)': + '@types/react-dom@19.2.2(@types/react@19.2.2)': dependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 '@types/react-syntax-highlighter@15.5.13': dependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@types/react-transition-group@4.4.12(@types/react@19.1.17)': + '@types/react-transition-group@4.4.12(@types/react@19.2.2)': dependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@types/react-virtualized-auto-sizer@1.0.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@types/react-virtualized-auto-sizer@1.0.8(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - react-virtualized-auto-sizer: 1.0.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-virtualized-auto-sizer: 1.0.26(react-dom@19.2.0(react@19.2.0))(react@19.2.0) transitivePeerDependencies: - react - react-dom '@types/react-window@1.8.8': dependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - '@types/react@19.1.17': + '@types/react@19.2.2': dependencies: csstype: 3.1.3 - '@types/reactcss@1.2.13(@types/react@19.1.17)': + '@types/reactcss@1.2.13(@types/react@19.2.2)': dependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 '@types/resolve@1.20.4': {} @@ -9139,14 +9139,14 @@ snapshots: clsx@2.1.1: {} - cmdk@1.1.1(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + cmdk@1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.17)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -9845,14 +9845,14 @@ snapshots: dependencies: fd-package-json: 2.0.0 - formik@2.4.6(react@19.1.1): + formik@2.4.6(react@19.2.0): dependencies: '@types/hoist-non-react-statics': 3.3.5 deepmerge: 2.2.1 hoist-non-react-statics: 3.3.2 lodash: 4.17.21 lodash-es: 4.17.21 - react: 19.1.1 + react: 19.2.0 react-fast-compare: 2.0.4 tiny-warning: 1.0.3 tslib: 2.6.2 @@ -10900,9 +10900,9 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@0.552.0(react@19.1.1): + lucide-react@0.552.0(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 luxon@3.3.0: {} @@ -11736,29 +11736,29 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - react-color@2.19.3(react@19.1.1): + react-color@2.19.3(react@19.2.0): dependencies: - '@icons/material': 0.2.4(react@19.1.1) + '@icons/material': 0.2.4(react@19.2.0) lodash: 4.17.21 lodash-es: 4.17.21 material-colors: 1.2.6 prop-types: 15.8.1 - react: 19.1.1 - reactcss: 1.2.3(react@19.1.1) + react: 19.2.0 + reactcss: 1.2.3(react@19.2.0) tinycolor2: 1.6.0 - react-confetti@6.4.0(react@19.1.1): + react-confetti@6.4.0(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 tween-functions: 1.2.0 - react-date-range@1.4.0(date-fns@2.30.0)(react@19.1.1): + react-date-range@1.4.0(date-fns@2.30.0)(react@19.2.0): dependencies: classnames: 2.3.2 date-fns: 2.30.0 prop-types: 15.8.1 - react: 19.1.1 - react-list: 0.8.17(react@19.1.1) + react: 19.2.0 + react-list: 0.8.17(react@19.2.0) shallow-equal: 1.2.1 react-docgen-typescript@2.2.2(typescript@5.6.3): @@ -11780,16 +11780,16 @@ snapshots: transitivePeerDependencies: - supports-color - react-dom@19.1.1(react@19.1.1): + react-dom@19.2.0(react@19.2.0): dependencies: - react: 19.1.1 - scheduler: 0.26.0 + react: 19.2.0 + scheduler: 0.27.0 react-fast-compare@2.0.4: {} - react-inspector@6.0.2(react@19.1.1): + react-inspector@6.0.2(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 react-is@16.13.1: {} @@ -11799,21 +11799,21 @@ snapshots: react-is@19.1.1: {} - react-list@0.8.17(react@19.1.1): + react-list@0.8.17(react@19.2.0): dependencies: prop-types: 15.8.1 - react: 19.1.1 + react: 19.2.0 - react-markdown@9.1.0(@types/react@19.1.17)(react@19.1.1): + react-markdown@9.1.0(@types/react@19.2.2)(react@19.2.0): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@types/react': 19.1.17 + '@types/react': 19.2.2 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 mdast-util-to-hast: 13.2.0 - react: 19.1.1 + react: 19.2.0 remark-parse: 11.0.0 remark-rehype: 11.1.2 unified: 11.0.5 @@ -11824,100 +11824,100 @@ snapshots: react-refresh@0.18.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.1.17)(react@19.1.1): + react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.1.1 - react-style-singleton: 2.2.3(@types/react@19.1.17)(react@19.1.1) + react: 19.2.0 + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - react-remove-scroll@2.7.1(@types/react@19.1.17)(react@19.1.1): + react-remove-scroll@2.7.1(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.1.1 - react-remove-scroll-bar: 2.3.8(@types/react@19.1.17)(react@19.1.1) - react-style-singleton: 2.2.3(@types/react@19.1.17)(react@19.1.1) + react: 19.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.2)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.1.17)(react@19.1.1) - use-sidecar: 1.1.3(@types/react@19.1.17)(react@19.1.1) + use-callback-ref: 1.3.3(@types/react@19.2.2)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - react-resizable-panels@3.0.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-resizable-panels@3.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: cookie: 1.0.2 - react: 19.1.1 + react: 19.2.0 set-cookie-parser: 2.7.2 optionalDependencies: - react-dom: 19.1.1(react@19.1.1) + react-dom: 19.2.0(react@19.2.0) - react-smooth@4.0.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-smooth@4.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: fast-equals: 5.3.2 prop-types: 15.8.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react-style-singleton@2.2.3(@types/react@19.1.17)(react@19.1.1): + react-style-singleton@2.2.3(@types/react@19.2.2)(react@19.2.0): dependencies: get-nonce: 1.0.1 - react: 19.1.1 + react: 19.2.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - react-syntax-highlighter@15.6.1(react@19.1.1): + react-syntax-highlighter@15.6.1(react@19.2.0): dependencies: '@babel/runtime': 7.26.10 highlight.js: 10.7.3 highlightjs-vue: 1.0.0 lowlight: 1.20.0 prismjs: 1.30.0 - react: 19.1.1 + react: 19.2.0 refractor: 3.6.0 - react-textarea-autosize@8.5.9(@types/react@19.1.17)(react@19.1.1): + react-textarea-autosize@8.5.9(@types/react@19.2.2)(react@19.2.0): dependencies: '@babel/runtime': 7.26.10 - react: 19.1.1 - use-composed-ref: 1.4.0(@types/react@19.1.17)(react@19.1.1) - use-latest: 1.3.0(@types/react@19.1.17)(react@19.1.1) + react: 19.2.0 + use-composed-ref: 1.4.0(@types/react@19.2.2)(react@19.2.0) + use-latest: 1.3.0(@types/react@19.2.2)(react@19.2.0) transitivePeerDependencies: - '@types/react' - react-transition-group@4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-transition-group@4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@babel/runtime': 7.26.10 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - react-virtualized-auto-sizer@1.0.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-virtualized-auto-sizer@1.0.26(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - react-window@1.8.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-window@1.8.11(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@babel/runtime': 7.26.10 memoize-one: 5.2.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - react@19.1.1: {} + react@19.2.0: {} - reactcss@1.2.3(react@19.1.1): + reactcss@1.2.3(react@19.2.0): dependencies: lodash: 4.17.21 - react: 19.1.1 + react: 19.2.0 read-cache@1.0.0: dependencies: @@ -11957,15 +11957,15 @@ snapshots: dependencies: decimal.js-light: 2.5.1 - recharts@2.15.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + recharts@2.15.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: clsx: 2.1.1 eventemitter3: 4.0.7 lodash: 4.17.21 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) react-is: 18.3.1 - react-smooth: 4.0.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-smooth: 4.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) recharts-scale: 0.4.5 tiny-invariant: 1.3.3 victory-vendor: 36.9.2 @@ -12116,7 +12116,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.26.0: {} + scheduler@0.27.0: {} semver@7.7.3: {} @@ -12261,16 +12261,16 @@ snapshots: dependencies: internal-slot: 1.0.6 - storybook-addon-remix-react-router@5.0.0(react-dom@19.1.1(react@19.1.1))(react-router@7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))): + storybook-addon-remix-react-router@5.0.0(react-dom@19.2.0(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0))): dependencies: '@mjackson/form-data-parser': 0.4.0 compare-versions: 6.1.0 - react-inspector: 6.0.2(react@19.1.1) - react-router: 7.9.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-inspector: 6.0.2(react@19.2.0) + react-router: 7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) storybook: 9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)) optionalDependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) storybook@9.1.16(@testing-library/dom@10.4.0)(msw@2.4.8(typescript@5.6.3))(prettier@3.4.1)(vite@7.1.12(@types/node@20.17.16)(jiti@1.21.7)(yaml@2.7.0)): dependencies: @@ -12667,43 +12667,43 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - use-callback-ref@1.3.3(@types/react@19.1.17)(react@19.1.1): + use-callback-ref@1.3.3(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - use-composed-ref@1.4.0(@types/react@19.1.17)(react@19.1.1): + use-composed-ref@1.4.0(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - use-isomorphic-layout-effect@1.2.1(@types/react@19.1.17)(react@19.1.1): + use-isomorphic-layout-effect@1.2.1(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - use-latest@1.3.0(@types/react@19.1.17)(react@19.1.1): + use-latest@1.3.0(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.1.1 - use-isomorphic-layout-effect: 1.2.1(@types/react@19.1.17)(react@19.1.1) + react: 19.2.0 + use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - use-sidecar@1.1.3(@types/react@19.1.17)(react@19.1.1): + use-sidecar@1.1.3(@types/react@19.2.2)(react@19.2.0): dependencies: detect-node-es: 1.1.0 - react: 19.1.1 + react: 19.2.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.17 + '@types/react': 19.2.2 - use-sync-external-store@1.6.0(react@19.1.1): + use-sync-external-store@1.6.0(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 util-deprecate@1.0.2: {} From e17b445e55165a3793cda9bf4d0b499f69f6f82e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:29:50 +0500 Subject: [PATCH 070/255] chore: bump @fontsource-variable/inter from 5.1.1 to 5.2.8 in /site (#20634) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package.json | 2 +- site/pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/package.json b/site/package.json index cb671274f8181..5de56e95fdeec 100644 --- a/site/package.json +++ b/site/package.json @@ -41,7 +41,7 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@emotion/styled": "11.14.1", - "@fontsource-variable/inter": "5.1.1", + "@fontsource-variable/inter": "5.2.8", "@fontsource/fira-code": "5.2.7", "@fontsource/ibm-plex-mono": "5.2.7", "@fontsource/jetbrains-mono": "5.2.8", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index c953784a890f1..dadc14a59c337 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -38,8 +38,8 @@ importers: specifier: 11.14.1 version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) '@fontsource-variable/inter': - specifier: 5.1.1 - version: 5.1.1 + specifier: 5.2.8 + version: 5.2.8 '@fontsource/fira-code': specifier: 5.2.7 version: 5.2.7 @@ -1042,8 +1042,8 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==, tarball: https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz} - '@fontsource-variable/inter@5.1.1': - resolution: {integrity: sha512-OpXFTmiH6tHkYijMvQTycFKBLK4X+SRV6tet1m4YOUH7SzIIlMqDja+ocDtiCA72UthBH/vF+3ZtlMr2rN/wIw==, tarball: https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.1.1.tgz} + '@fontsource-variable/inter@5.2.8': + resolution: {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==, tarball: https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz} '@fontsource/fira-code@5.2.7': resolution: {integrity: sha512-tnB9NNund9TwIym8/7DMJe573nlPEQb+fKUV5GL8TBYXjIhDvL0D7mgmNVNQUPhXp+R7RylQeiBdkA4EbOHPGQ==, tarball: https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-5.2.7.tgz} @@ -6943,7 +6943,7 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@fontsource-variable/inter@5.1.1': {} + '@fontsource-variable/inter@5.2.8': {} '@fontsource/fira-code@5.2.7': {} From 926369b9f2c4152f51a0b621f0298fd2a7e341de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:40:12 +0000 Subject: [PATCH 071/255] chore: bump coder/jetbrains/coder from 1.1.0 to 1.1.1 in /dogfood/coder (#20643) [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=coder/jetbrains/coder&package-manager=terraform&previous-version=1.1.0&new-version=1.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dogfood/coder/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dogfood/coder/main.tf b/dogfood/coder/main.tf index 4064544b6e6fa..344bc6970a326 100644 --- a/dogfood/coder/main.tf +++ b/dogfood/coder/main.tf @@ -385,7 +385,7 @@ module "vscode-web" { module "jetbrains" { count = contains(jsondecode(data.coder_parameter.ide_choices.value), "jetbrains") ? data.coder_workspace.me.start_count : 0 source = "dev.registry.coder.com/coder/jetbrains/coder" - version = "1.1.0" + version = "1.1.1" agent_id = coder_agent.dev.id agent_name = "dev" folder = local.repo_dir From eef18424e3768626ed3711f0b083bbaa6e679453 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:40:27 +0000 Subject: [PATCH 072/255] chore: bump coder/claude-code/coder from 3.3.2 to 3.4.4 in /dogfood/coder (#20642) [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=coder/claude-code/coder&package-manager=terraform&previous-version=3.3.2&new-version=3.4.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dogfood/coder/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dogfood/coder/main.tf b/dogfood/coder/main.tf index 344bc6970a326..c5328c06e4a1a 100644 --- a/dogfood/coder/main.tf +++ b/dogfood/coder/main.tf @@ -826,7 +826,7 @@ locals { module "claude-code" { count = local.has_ai_prompt ? data.coder_workspace.me.start_count : 0 source = "dev.registry.coder.com/coder/claude-code/coder" - version = "3.3.2" + version = "3.4.4" agent_id = coder_agent.dev.id workdir = local.repo_dir claude_code_version = "latest" From 2a5d86e2aaad211188591a5f28480a891c0e034f Mon Sep 17 00:00:00 2001 From: david-fraley <67079030+david-fraley@users.noreply.github.com> Date: Sun, 2 Nov 2025 19:12:37 -0600 Subject: [PATCH 073/255] docs: add vacuum full on `audit logs` table recommendation (#20608) --- docs/admin/security/audit-logs.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index 387bdd9836a19..75c349aee21ee 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -151,6 +151,36 @@ Should you wish to purge these records, it is safe to do so. This can only be do directly against the `audit_logs` table in the database. We advise users to only purge old records (>1yr) and in accordance with your compliance requirements. +### Maintenance Procedures for the Audit Logs Table + +> [!NOTE] +> `VACUUM FULL` acquires an exclusive lock on the table, blocking all reads and writes. For more information, see the [PostgreSQL VACUUM documentation](https://www.postgresql.org/docs/current/sql-vacuum.html). + +You may choose to run a `VACUUM` or `VACUUM FULL` operation on the audit logs table to reclaim disk space. If you choose to run the `FULL` operation, consider the following when doing so: + +- **Run during a planned mainteance window** to ensure ample time for the operation to complete and minimize impact to users +- **Stop all running instances of `coderd`** to prevent connection errors while the table is locked. The actual steps for this will depend on your particular deployment setup. For example, if your `coderd` deployment is running on Kubernetes: + + ```bash + kubectl scale deployment coder --replicas=0 -n coder + ``` + +- **Terminate lingering connections** before running the `VACUUM` operation to ensure it starts immediately + + ```sql + SELECT pg_terminate_backend(pg_stat_activity.pid) + FROM pg_stat_activity + WHERE pg_stat_activity.datname = 'coder' AND pid <> pg_backend_pid(); + ``` + +- **Only `coderd` needs to scale down** - external provisioner daemons, workspace proxies, and workspace agents don't connect to the database directly. + +After the vacuum completes, scale coderd back up: + +```bash +kubectl scale deployment coder --replicas= -n coder +``` + ### Backup/Archive Consider exporting or archiving these records before deletion: From 26d029022d8142d76403937f0e294351640685e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 13:51:18 +0500 Subject: [PATCH 074/255] chore(examples/templates/tasks-docker): bump coder/claude-code to 3.4.4 (#20644) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/templates/tasks-docker/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/templates/tasks-docker/main.tf b/examples/templates/tasks-docker/main.tf index 8a457584a4674..70deae363c997 100644 --- a/examples/templates/tasks-docker/main.tf +++ b/examples/templates/tasks-docker/main.tf @@ -22,7 +22,7 @@ provider "docker" {} module "claude-code" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/claude-code/coder" - version = "3.3.2" + version = "3.4.4" agent_id = coder_agent.main.id workdir = "/home/coder/projects" order = 999 From 3d411ddf4c9500781268b14939f99fbcef3eed7c Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Mon, 3 Nov 2025 14:02:19 +0500 Subject: [PATCH 075/255] chore: correct tooltip for JetBrains module in templates (#20638) --- dogfood/coder/main.tf | 2 +- examples/templates/docker/main.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dogfood/coder/main.tf b/dogfood/coder/main.tf index c5328c06e4a1a..8b2d083e3b5f2 100644 --- a/dogfood/coder/main.tf +++ b/dogfood/coder/main.tf @@ -390,7 +390,7 @@ module "jetbrains" { agent_name = "dev" folder = local.repo_dir major_version = "latest" - tooltip = "You need to [Install Coder Desktop](https://coder.com/docs/user-guides/desktop#install-coder-desktop) to use this button." + tooltip = "You need to [install JetBrains Toolbox](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) to use this app." } module "filebrowser" { diff --git a/examples/templates/docker/main.tf b/examples/templates/docker/main.tf index a3535042b0799..7bb580e514920 100644 --- a/examples/templates/docker/main.tf +++ b/examples/templates/docker/main.tf @@ -141,7 +141,7 @@ module "jetbrains" { agent_id = coder_agent.main.id agent_name = "main" folder = "/home/coder" - tooltip = "You need to [Install Coder Desktop](https://coder.com/docs/user-guides/desktop#install-coder-desktop) to use this button." + tooltip = "You need to [install JetBrains Toolbox](https://coder.com/docs/user-guides/workspace-access/jetbrains/toolbox) to use this app." } resource "docker_volume" "home_volume" { From 11f24119c5699bf4bc93ca5154246d32977b6587 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 3 Nov 2025 10:13:19 +0000 Subject: [PATCH 076/255] chore(cli): fix issue with running individual sub-tests of Test_Tasks (#20646) Closes https://github.com/coder/internal/issues/1112 Turns out sub-tests aren't ideal when you're running things that depend on each other and need to be run in a certain order. --- cli/exp_task_test.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cli/exp_task_test.go b/cli/exp_task_test.go index 71732902497b1..f794e3a0774a5 100644 --- a/cli/exp_task_test.go +++ b/cli/exp_task_test.go @@ -53,7 +53,6 @@ func Test_Tasks(t *testing.T) { taskName = strings.ReplaceAll(testutil.GetRandomName(t), "_", "-") ) - //nolint:paralleltest // The sub-tests of this test must be run sequentially. for _, tc := range []struct { name string cmdArgs []string @@ -135,16 +134,15 @@ func Test_Tasks(t *testing.T) { }, }, } { - t.Run(tc.name, func(t *testing.T) { - var stdout strings.Builder - inv, root := clitest.New(t, tc.cmdArgs...) - inv.Stdout = &stdout - clitest.SetupConfig(t, userClient, root) - require.NoError(t, inv.WithContext(ctx).Run()) - if tc.assertFn != nil { - tc.assertFn(stdout.String(), userClient) - } - }) + t.Logf("test case: %q", tc.name) + var stdout strings.Builder + inv, root := clitest.New(t, tc.cmdArgs...) + inv.Stdout = &stdout + clitest.SetupConfig(t, userClient, root) + require.NoError(t, inv.WithContext(ctx).Run(), tc.name) + if tc.assertFn != nil { + tc.assertFn(stdout.String(), userClient) + } } } From 1961252918bf8a2cc23e5de6e11a90e6a3de9d9a Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 3 Nov 2025 11:39:02 +0000 Subject: [PATCH 077/255] chore(coderd/provisionerdserver): address flake in TestServer_ExpirePrebuildsSessionToken (#20648) Addresses a flake seen locally by @mafredri: ``` panic: interface conversion: proto.isAcquiredJob_Type is nil, not *proto.AcquiredJob_WorkspaceBuild_ [recovered] panic: interface conversion: proto.isAcquiredJob_Type is nil, not *proto.AcquiredJob_WorkspaceBuild_ goroutine 77 [running]: testing.tRunner.func1.2({0x35ba440, 0xc000f15620}) /usr/local/go/src/testing/testing.go:1734 +0x21c testing.tRunner.func1() /usr/local/go/src/testing/testing.go:1737 +0x35e panic({0x35ba440?, 0xc000f15620?}) /usr/local/go/src/runtime/panic.go:792 +0x132 github.com/coder/coder/v2/coderd/provisionerdserver_test.TestServer_ExpirePrebuildsSessionToken(0xc00010d500) /home/coder/coder/coderd/provisionerdserver/provisionerdserver_test.go:4128 +0xc4b testing.tRunner(0xc00010d500, 0x4bd8450) /usr/local/go/src/testing/testing.go:1792 +0xf4 created by testing.(*T).Run in goroutine 1 /usr/local/go/src/testing/testing.go:1851 +0x413 FAIL github.com/coder/coder/v2/coderd/provisionerdserver 20.830s FAIL ``` It's unclear why this would happen in the first place. --- coderd/provisionerdserver/provisionerdserver_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 8d55e1529289f..850d95d7549e4 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -4091,6 +4091,7 @@ func TestServer_ExpirePrebuildsSessionToken(t *testing.T) { job, err := fs.waitForJob() require.NoError(t, err) require.NotNil(t, job) + require.NotNil(t, job.Type, "acquired job type was nil?!") workspaceBuildJob := job.Type.(*proto.AcquiredJob_WorkspaceBuild_).WorkspaceBuild require.NotNil(t, workspaceBuildJob.Metadata) From a6b0eae38d9d9272b10ff1172de39524bd06f4b2 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 3 Nov 2025 13:46:38 +0200 Subject: [PATCH 078/255] refactor(coderd): drop sidebar app constraint and simplify provisionerdserver for tasks (#20591) Updates coder/internal#973 Updates coder/internal#974 --- coderd/database/check_constraint.go | 21 +++-- coderd/database/dump.sql | 1 - ...d_ai_task_sidebar_app_id_required.down.sql | 4 + ...ild_ai_task_sidebar_app_id_required.up.sql | 4 + .../provisionerdserver/provisionerdserver.go | 86 ++++++------------- .../provisionerdserver_test.go | 68 +++++++++++---- 6 files changed, 93 insertions(+), 91 deletions(-) create mode 100644 coderd/database/migrations/000394_drop_workspace_build_ai_task_sidebar_app_id_required.down.sql create mode 100644 coderd/database/migrations/000394_drop_workspace_build_ai_task_sidebar_app_id_required.up.sql diff --git a/coderd/database/check_constraint.go b/coderd/database/check_constraint.go index 8b1917b7697db..c8752b207de16 100644 --- a/coderd/database/check_constraint.go +++ b/coderd/database/check_constraint.go @@ -6,15 +6,14 @@ type CheckConstraint string // CheckConstraint enums. const ( - CheckAPIKeysAllowListNotEmpty CheckConstraint = "api_keys_allow_list_not_empty" // api_keys - CheckOneTimePasscodeSet CheckConstraint = "one_time_passcode_set" // users - CheckUsersUsernameMinLength CheckConstraint = "users_username_min_length" // users - CheckMaxProvisionerLogsLength CheckConstraint = "max_provisioner_logs_length" // provisioner_jobs - CheckMaxLogsLength CheckConstraint = "max_logs_length" // workspace_agents - CheckSubsystemsNotNone CheckConstraint = "subsystems_not_none" // workspace_agents - CheckWorkspaceBuildsAiTaskSidebarAppIDRequired CheckConstraint = "workspace_builds_ai_task_sidebar_app_id_required" // workspace_builds - CheckWorkspaceBuildsDeadlineBelowMaxDeadline CheckConstraint = "workspace_builds_deadline_below_max_deadline" // workspace_builds - CheckTelemetryLockEventTypeConstraint CheckConstraint = "telemetry_lock_event_type_constraint" // telemetry_locks - CheckValidationMonotonicOrder CheckConstraint = "validation_monotonic_order" // template_version_parameters - CheckUsageEventTypeCheck CheckConstraint = "usage_event_type_check" // usage_events + CheckAPIKeysAllowListNotEmpty CheckConstraint = "api_keys_allow_list_not_empty" // api_keys + CheckOneTimePasscodeSet CheckConstraint = "one_time_passcode_set" // users + CheckUsersUsernameMinLength CheckConstraint = "users_username_min_length" // users + CheckMaxProvisionerLogsLength CheckConstraint = "max_provisioner_logs_length" // provisioner_jobs + CheckMaxLogsLength CheckConstraint = "max_logs_length" // workspace_agents + CheckSubsystemsNotNone CheckConstraint = "subsystems_not_none" // workspace_agents + CheckWorkspaceBuildsDeadlineBelowMaxDeadline CheckConstraint = "workspace_builds_deadline_below_max_deadline" // workspace_builds + CheckTelemetryLockEventTypeConstraint CheckConstraint = "telemetry_lock_event_type_constraint" // telemetry_locks + CheckValidationMonotonicOrder CheckConstraint = "validation_monotonic_order" // template_version_parameters + CheckUsageEventTypeCheck CheckConstraint = "usage_event_type_check" // usage_events ) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 8790bd27df693..f08454a89731d 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1950,7 +1950,6 @@ CREATE TABLE workspace_builds ( has_ai_task boolean, ai_task_sidebar_app_id uuid, has_external_agent boolean, - CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required CHECK (((((has_ai_task IS NULL) OR (has_ai_task = false)) AND (ai_task_sidebar_app_id IS NULL)) OR ((has_ai_task = true) AND (ai_task_sidebar_app_id IS NOT NULL)))), CONSTRAINT workspace_builds_deadline_below_max_deadline CHECK ((((deadline <> '0001-01-01 00:00:00+00'::timestamp with time zone) AND (deadline <= max_deadline)) OR (max_deadline = '0001-01-01 00:00:00+00'::timestamp with time zone))) ); diff --git a/coderd/database/migrations/000394_drop_workspace_build_ai_task_sidebar_app_id_required.down.sql b/coderd/database/migrations/000394_drop_workspace_build_ai_task_sidebar_app_id_required.down.sql new file mode 100644 index 0000000000000..c079189235a62 --- /dev/null +++ b/coderd/database/migrations/000394_drop_workspace_build_ai_task_sidebar_app_id_required.down.sql @@ -0,0 +1,4 @@ +-- WARNING: Restoring this constraint after running a newer version of coderd +-- and using tasks is bound to break this constraint. +ALTER TABLE workspace_builds +ADD CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required CHECK (((((has_ai_task IS NULL) OR (has_ai_task = false)) AND (ai_task_sidebar_app_id IS NULL)) OR ((has_ai_task = true) AND (ai_task_sidebar_app_id IS NOT NULL)))); diff --git a/coderd/database/migrations/000394_drop_workspace_build_ai_task_sidebar_app_id_required.up.sql b/coderd/database/migrations/000394_drop_workspace_build_ai_task_sidebar_app_id_required.up.sql new file mode 100644 index 0000000000000..4703b6f764a56 --- /dev/null +++ b/coderd/database/migrations/000394_drop_workspace_build_ai_task_sidebar_app_id_required.up.sql @@ -0,0 +1,4 @@ +-- We no longer need to enforce this constraint as tasks have their own data +-- model. +ALTER TABLE workspace_builds +DROP CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required; diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 2e00796d1cd64..7c96c36eab163 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -2006,10 +2006,12 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro } } - var taskAppID uuid.NullUUID - var taskAgentID uuid.NullUUID - var hasAITask bool - var warnUnknownTaskAppID bool + var ( + hasAITask bool + unknownAppID string + taskAppID uuid.NullUUID + taskAgentID uuid.NullUUID + ) if tasks := jobType.WorkspaceBuild.GetAiTasks(); len(tasks) > 0 { hasAITask = true task := tasks[0] @@ -2026,59 +2028,29 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro } if !slices.Contains(appIDs, appID) { - warnUnknownTaskAppID = true - } - - id, err := uuid.Parse(appID) - if err != nil { - return xerrors.Errorf("parse app id: %w", err) - } - - taskAppID = uuid.NullUUID{UUID: id, Valid: true} - - agentID, ok := agentIDByAppID[appID] - taskAgentID = uuid.NullUUID{UUID: agentID, Valid: ok} - } - - // This is a hacky workaround for the issue with tasks 'disappearing' on stop: - // reuse has_ai_task and sidebar_app_id from the previous build. - // This workaround should be removed as soon as possible. - if workspaceBuild.Transition == database.WorkspaceTransitionStop && workspaceBuild.BuildNumber > 1 { - if prevBuild, err := s.Database.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{ - WorkspaceID: workspaceBuild.WorkspaceID, - BuildNumber: workspaceBuild.BuildNumber - 1, - }); err == nil { - hasAITask = prevBuild.HasAITask.Bool - taskAppID = prevBuild.AITaskSidebarAppID - warnUnknownTaskAppID = false - s.Logger.Debug(ctx, "task workaround: reused has_ai_task and app_id from previous build to keep track of task", - slog.F("job_id", job.ID.String()), - slog.F("build_number", prevBuild.BuildNumber), - slog.F("workspace_id", workspace.ID), - slog.F("workspace_build_id", workspaceBuild.ID), - slog.F("transition", string(workspaceBuild.Transition)), - slog.F("sidebar_app_id", taskAppID.UUID), - slog.F("has_ai_task", hasAITask), - ) + unknownAppID = appID + hasAITask = false } else { - s.Logger.Error(ctx, "task workaround: tracking via has_ai_task and app_id from previous build failed", - slog.Error(err), - slog.F("job_id", job.ID.String()), - slog.F("workspace_id", workspace.ID), - slog.F("workspace_build_id", workspaceBuild.ID), - slog.F("transition", string(workspaceBuild.Transition)), - ) + // Only parse for valid app and agent to avoid fk violation. + id, err := uuid.Parse(appID) + if err != nil { + return xerrors.Errorf("parse app id: %w", err) + } + taskAppID = uuid.NullUUID{UUID: id, Valid: true} + + agentID, ok := agentIDByAppID[appID] + taskAgentID = uuid.NullUUID{UUID: agentID, Valid: ok} } } - if warnUnknownTaskAppID { + if unknownAppID != "" && workspaceBuild.Transition == database.WorkspaceTransitionStart { // Ref: https://github.com/coder/coder/issues/18776 // This can happen for a number of reasons: // 1. Misconfigured template // 2. Count=0 on the agent due to stop transition, meaning the associated coder_app was not inserted. // Failing the build at this point is not ideal, so log a warning instead. s.Logger.Warn(ctx, "unknown ai_task_app_id", - slog.F("ai_task_app_id", taskAppID.UUID.String()), + slog.F("ai_task_app_id", unknownAppID), slog.F("job_id", job.ID.String()), slog.F("workspace_id", workspace.ID), slog.F("workspace_build_id", workspaceBuild.ID), @@ -2105,9 +2077,6 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro slog.F("transition", string(workspaceBuild.Transition)), ) } - // Important: reset hasAITask and sidebarAppID so that we don't run into a fk constraint violation. - hasAITask = false - taskAppID = uuid.NullUUID{} } if hasAITask && workspaceBuild.Transition == database.WorkspaceTransitionStart { @@ -2124,14 +2093,6 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro } } - hasExternalAgent := false - for _, resource := range jobType.WorkspaceBuild.Resources { - if resource.Type == "coder_external_agent" { - hasExternalAgent = true - break - } - } - if task, err := db.GetTaskByWorkspaceID(ctx, workspace.ID); err == nil { // Irrespective of whether the agent or sidebar app is present, // perform the upsert to ensure a link between the task and @@ -2153,20 +2114,21 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro return xerrors.Errorf("get task by workspace id: %w", err) } - // Regardless of whether there is an AI task or not, update the field to indicate one way or the other since it - // always defaults to nil. ONLY if has_ai_task=true MUST ai_task_sidebar_app_id be set. + _, hasExternalAgent := slice.Find(jobType.WorkspaceBuild.Resources, func(resource *sdkproto.Resource) bool { + return resource.Type == "coder_external_agent" + }) if err := db.UpdateWorkspaceBuildFlagsByID(ctx, database.UpdateWorkspaceBuildFlagsByIDParams{ ID: workspaceBuild.ID, HasAITask: sql.NullBool{ Bool: hasAITask, Valid: true, }, + SidebarAppID: taskAppID, // SidebarAppID is not required, but kept for API backwards compatibility. HasExternalAgent: sql.NullBool{ Bool: hasExternalAgent, Valid: true, }, - SidebarAppID: taskAppID, - UpdatedAt: now, + UpdatedAt: now, }); err != nil { return xerrors.Errorf("update workspace build ai tasks and external agent flag: %w", err) } diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 850d95d7549e4..c4644e0a66abd 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -2864,11 +2864,12 @@ func TestCompleteJob(t *testing.T) { input *proto.CompletedJob_WorkspaceBuild isTask bool expectTaskStatus database.TaskStatus + expectAppID uuid.NullUUID expectHasAiTask bool expectUsageEvent bool } - sidebarAppID := uuid.NewString() + sidebarAppID := uuid.New() for _, tc := range []testcase{ { name: "has_ai_task is false by default", @@ -2883,12 +2884,45 @@ func TestCompleteJob(t *testing.T) { { name: "has_ai_task is set to true", transition: database.WorkspaceTransitionStart, + input: &proto.CompletedJob_WorkspaceBuild{ + AiTasks: []*sdkproto.AITask{ + { + Id: uuid.NewString(), + AppId: sidebarAppID.String(), + }, + }, + Resources: []*sdkproto.Resource{ + { + Agents: []*sdkproto.Agent{ + { + Id: uuid.NewString(), + Name: "a", + Apps: []*sdkproto.App{ + { + Id: sidebarAppID.String(), + Slug: "test-app", + }, + }, + }, + }, + }, + }, + }, + isTask: true, + expectTaskStatus: database.TaskStatusInitializing, + expectAppID: uuid.NullUUID{UUID: sidebarAppID, Valid: true}, + expectHasAiTask: true, + expectUsageEvent: true, + }, + { + name: "has_ai_task is set to true, with sidebar app id", + transition: database.WorkspaceTransitionStart, input: &proto.CompletedJob_WorkspaceBuild{ AiTasks: []*sdkproto.AITask{ { Id: uuid.NewString(), SidebarApp: &sdkproto.AITaskSidebarApp{ - Id: sidebarAppID, + Id: sidebarAppID.String(), }, }, }, @@ -2900,7 +2934,7 @@ func TestCompleteJob(t *testing.T) { Name: "a", Apps: []*sdkproto.App{ { - Id: sidebarAppID, + Id: sidebarAppID.String(), Slug: "test-app", }, }, @@ -2911,6 +2945,7 @@ func TestCompleteJob(t *testing.T) { }, isTask: true, expectTaskStatus: database.TaskStatusInitializing, + expectAppID: uuid.NullUUID{UUID: sidebarAppID, Valid: true}, expectHasAiTask: true, expectUsageEvent: true, }, @@ -2922,10 +2957,9 @@ func TestCompleteJob(t *testing.T) { AiTasks: []*sdkproto.AITask{ { Id: uuid.NewString(), - SidebarApp: &sdkproto.AITaskSidebarApp{ - // Non-existing app ID would previously trigger a FK violation. - Id: uuid.NewString(), - }, + // Non-existing app ID would previously trigger a FK violation. + // Now it should just be ignored. + AppId: sidebarAppID.String(), }, }, }, @@ -2940,10 +2974,8 @@ func TestCompleteJob(t *testing.T) { input: &proto.CompletedJob_WorkspaceBuild{ AiTasks: []*sdkproto.AITask{ { - Id: uuid.NewString(), - SidebarApp: &sdkproto.AITaskSidebarApp{ - Id: sidebarAppID, - }, + Id: uuid.NewString(), + AppId: sidebarAppID.String(), }, }, Resources: []*sdkproto.Resource{ @@ -2954,7 +2986,7 @@ func TestCompleteJob(t *testing.T) { Name: "a", Apps: []*sdkproto.App{ { - Id: sidebarAppID, + Id: sidebarAppID.String(), Slug: "test-app", }, }, @@ -2965,6 +2997,7 @@ func TestCompleteJob(t *testing.T) { }, isTask: true, expectTaskStatus: database.TaskStatusPaused, + expectAppID: uuid.NullUUID{UUID: sidebarAppID, Valid: true}, expectHasAiTask: true, expectUsageEvent: false, }, @@ -2978,7 +3011,7 @@ func TestCompleteJob(t *testing.T) { }, isTask: true, expectTaskStatus: database.TaskStatusPaused, - expectHasAiTask: true, + expectHasAiTask: false, // We no longer inherit this from the previous build. expectUsageEvent: false, }, } { @@ -3092,15 +3125,16 @@ func TestCompleteJob(t *testing.T) { require.True(t, build.HasAITask.Valid) // We ALWAYS expect a value to be set, therefore not nil, i.e. valid = true. require.Equal(t, tc.expectHasAiTask, build.HasAITask.Bool) + task, err := db.GetTaskByID(ctx, genTask.ID) if tc.isTask { - task, err := db.GetTaskByID(ctx, genTask.ID) require.NoError(t, err) require.Equal(t, tc.expectTaskStatus, task.Status) + } else { + require.Error(t, err) } - if tc.expectHasAiTask && build.Transition != database.WorkspaceTransitionStop { - require.Equal(t, sidebarAppID, build.AITaskSidebarAppID.UUID.String()) - } + require.Equal(t, tc.expectAppID, task.WorkspaceAppID) + require.Equal(t, tc.expectAppID, build.AITaskSidebarAppID) if tc.expectUsageEvent { // Check that a usage event was collected. From 891274838e7e3cf3402690ae0046d8249e07eaa8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:15:18 +0000 Subject: [PATCH 079/255] chore: bump google.golang.org/api from 0.253.0 to 0.254.0 (#20653) Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.253.0 to 0.254.0.
Release notes

Sourced from google.golang.org/api's releases.

v0.254.0

0.254.0 (2025-10-28)

Features

Changelog

Sourced from google.golang.org/api's changelog.

0.254.0 (2025-10-28)

Features

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/api&package-manager=go_modules&previous-version=0.253.0&new-version=0.254.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 45c8189eada3d..95b9528d3c50a 100644 --- a/go.mod +++ b/go.mod @@ -206,7 +206,7 @@ require ( golang.org/x/text v0.30.0 golang.org/x/tools v0.38.0 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da - google.golang.org/api v0.253.0 + google.golang.org/api v0.254.0 google.golang.org/grpc v1.76.0 google.golang.org/protobuf v1.36.10 gopkg.in/DataDog/dd-trace-go.v1 v1.74.0 @@ -453,7 +453,7 @@ require ( google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect gopkg.in/ini.v1 v1.67.0 // indirect howett.net/plist v1.0.0 // indirect kernel.org/pub/linux/libs/security/libcap/psx v1.2.73 // indirect diff --git a/go.sum b/go.sum index 615b1ecbe5ec8..4b608193adf47 100644 --- a/go.sum +++ b/go.sum @@ -2543,8 +2543,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.253.0 h1:apU86Eq9Q2eQco3NsUYFpVTfy7DwemojL7LmbAj7g/I= -google.golang.org/api v0.253.0/go.mod h1:PX09ad0r/4du83vZVAaGg7OaeyGnaUmT/CYPNvtLCbw= +google.golang.org/api v0.254.0 h1:jl3XrGj7lRjnlUvZAbAdhINTLbsg5dbjmR90+pTQvt4= +google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2689,8 +2689,8 @@ google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJ google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= From 9c22ae847e21a70e4cfec215497666122ba8f89b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:15:28 +0000 Subject: [PATCH 080/255] chore: bump github.com/anthropics/anthropic-sdk-go from 1.13.0 to 1.16.0 (#20654) Bumps [github.com/anthropics/anthropic-sdk-go](https://github.com/anthropics/anthropic-sdk-go) from 1.13.0 to 1.16.0.
Release notes

Sourced from github.com/anthropics/anthropic-sdk-go's releases.

v1.16.0

1.16.0 (2025-10-29)

Full Changelog: v1.15.0...v1.16.0

Features

  • api: add ability to clear thinking in context management (6082754)

v1.15.0

1.15.0 (2025-10-28)

Full Changelog: v1.14.0...v1.15.0

Features

  • api: adding support for agent skills (5660b52)

Chores

  • api: mark older sonnet models as deprecated (f13c5bd)

v1.14.0

1.14.0 (2025-10-15)

Full Changelog: v1.13.0...v1.14.0

Features

Chores

  • client: add context-management-2025-06-27 beta header (eeba6fa)
  • client: add model-context-window-exceeded-2025-08-26 beta header (7d5a37d)
Changelog

Sourced from github.com/anthropics/anthropic-sdk-go's changelog.

1.16.0 (2025-10-29)

Full Changelog: v1.15.0...v1.16.0

Features

  • api: add ability to clear thinking in context management (6082754)

1.15.0 (2025-10-28)

Full Changelog: v1.14.0...v1.15.0

Features

  • api: adding support for agent skills (5660b52)

Chores

  • api: mark older sonnet models as deprecated (f13c5bd)

1.14.0 (2025-10-15)

Full Changelog: v1.13.0...v1.14.0

Features

Chores

  • client: add context-management-2025-06-27 beta header (eeba6fa)
  • client: add model-context-window-exceeded-2025-08-26 beta header (7d5a37d)
Commits
  • 3a0275d Merge pull request #247 from anthropics/release-please--branches--main--chang...
  • 368ce71 release: 1.16.0
  • 6082754 feat(api): add ability to clear thinking in context management
  • b2bfccc Merge pull request #244 from anthropics/release-please--branches--main--chang...
  • bf69f4b release: 1.15.0
  • f13c5bd chore(api): mark older sonnet models as deprecated
  • 5660b52 feat(api): adding support for agent skills
  • 7fbe034 release: 1.14.0
  • e678457 feat(api): manual updates
  • 87e262b codegen metadata
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/anthropics/anthropic-sdk-go&package-manager=go_modules&previous-version=1.13.0&new-version=1.16.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 95b9528d3c50a..68e16cc5dbdb3 100644 --- a/go.mod +++ b/go.mod @@ -473,7 +473,7 @@ require ( ) require ( - github.com/anthropics/anthropic-sdk-go v1.13.0 + github.com/anthropics/anthropic-sdk-go v1.16.0 github.com/brianvoe/gofakeit/v7 v7.8.0 github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 github.com/coder/aibridge v0.1.6 diff --git a/go.sum b/go.sum index 4b608193adf47..8aa070e277f1d 100644 --- a/go.sum +++ b/go.sum @@ -726,8 +726,8 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/anthropics/anthropic-sdk-go v1.13.0 h1:Bhbe8sRoDPtipttg8bQYrMCKe2b79+q6rFW1vOKEUKI= -github.com/anthropics/anthropic-sdk-go v1.13.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/anthropics/anthropic-sdk-go v1.16.0 h1:nRkOFDqYXsHteoIhjdJr/5dsiKbFF3rflSv8ax50y8o= +github.com/anthropics/anthropic-sdk-go v1.16.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= From a34897817fa2cf3fc1b545c77740c0a68413361b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:19:52 +0000 Subject: [PATCH 081/255] ci: bump the github-actions group with 6 updates (#20656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 6 updates: | Package | From | To | | --- | --- | --- | | [crate-ci/typos](https://github.com/crate-ci/typos) | `1.38.1` | `1.39.0` | | [chromaui/action](https://github.com/chromaui/action) | `13.3.2` | `13.3.3` | | [fluxcd/flux2](https://github.com/fluxcd/flux2) | `2.7.2` | `2.7.3` | | [tj-actions/changed-files](https://github.com/tj-actions/changed-files) | `dbf178ceecb9304128c8e0648591d71208c6e2c9` | `0ff001de0805038ff3f118de4875002200057732` | | [github/codeql-action](https://github.com/github/codeql-action) | `4.31.0` | `4.31.2` | | [Mattraks/delete-workflow-runs](https://github.com/mattraks/delete-workflow-runs) | `ab482449ba468316e9a8801e092d0405715c5e6d` | `86d29a75093353c4c509a876c176234037c2025b` | Updates `crate-ci/typos` from 1.38.1 to 1.39.0
Release notes

Sourced from crate-ci/typos's releases.

v1.39.0

[1.39.0] - 2025-10-31

Features

Fixes

  • When a typo is pluralized, prefer pluralized corrections
Changelog

Sourced from crate-ci/typos's changelog.

Change Log

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

[Unreleased] - ReleaseDate

[1.39.0] - 2025-10-31

Features

Fixes

  • When a typo is pluralized, prefer pluralized corrections

[1.38.1] - 2025-10-07

Fixes

  • Ignore common golang identifiers

[1.38.0] - 2025-10-06

Features

  • Update type list

Fixes

  • Don't correct typ
  • Consistently error on unused config fields

[1.37.3] - 2025-10-06

Fixes

  • Don't correct PN for bitbake file types

[1.37.2] - 2025-10-03

Fixes

  • Don't suggest diagnostic for diagnotics, preferring diagnostics

[1.37.1] - 2025-10-01

... (truncated)

Commits

Updates `chromaui/action` from 13.3.2 to 13.3.3
Commits

Updates `fluxcd/flux2` from 2.7.2 to 2.7.3
Release notes

Sourced from fluxcd/flux2's releases.

v2.7.3

Highlights

Flux v2.7.3 is a patch release that comes with various fixes. Users are encouraged to upgrade for the best experience.

ℹ️ Please follow the Upgrade Procedure for Flux v2.7+ for a smooth upgrade from Flux v2.6 to the latest version.

Fixes:

  • Restore SOCKS5 proxy support in all controllers
  • Fix status reporting of HelmReleases with RetryOnFailure strategy
  • Automated retries for ImagePolicies when no image tags are found in the database
  • Fix alerting for Telegram's message_thread_id
  • Allow running kustomize-controller and helm-controller on the same loopback interface as source-watcher

:warning: Note that signature verification for OCI artifacts in source-controller is not compatible with Cosign v3. Users are advised to use Cosign v2.6 for signing Flux OCI artifacts and Helm charts, until support for Cosign v3 is added in Flux v2.8.

Components changelog

CLI changelog

Full Changelog: https://github.com/fluxcd/flux2/compare/v2.7.2...v2.7.3

Commits
  • b6e76ca Merge pull request #5606 from fluxcd/backport-5602-to-release/v2.7.x
  • e084250 fix: return supported values for flags when calling Values.Type()
  • c3bc3d5 Merge pull request #5605 from fluxcd/backport-5603-to-release/v2.7.x
  • 1295ba2 Fix bootstrap e2e test for image policy
  • 41ebc0e Update toolkit components
  • 67d2fb0 Merge pull request #5595 from fluxcd/backport-5594-to-release/v2.7.x
  • 888e8a9 Pin cosign to v2.6.1
  • See full diff in compare view

Updates `tj-actions/changed-files` from dbf178ceecb9304128c8e0648591d71208c6e2c9 to 0ff001de0805038ff3f118de4875002200057732
Changelog

Sourced from tj-actions/changed-files's changelog.

Changelog

47.0.0 - (2025-09-13)

🚀 Features

➖ Remove

  • Commit and push step from build job (#2538) (be393a9) - (Tonye Jack)

🔄 Update

  • Updated README.md (#2592)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@​users.noreply.github.com> (3dbc1e1) - (github-actions[bot])

  • Updated README.md (#2591)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@​users.noreply.github.com> (b1ccff8) - (github-actions[bot])

  • Updated README.md (#2574)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@​users.noreply.github.com> (050a3d3) - (github-actions[bot])

📚 Documentation

  • Update link to glob patterns (#2590) (a892f50) - (Tonye Jack)
  • Add Jellyfrog as a contributor for code, and doc (#2573) (f000a9b) - (allcontributors[bot])

🧪 Testing

  • Manual triggered workflows (#2637) (c2ca249) - (Tonye Jack)

⚙️ Miscellaneous Tasks

  • deps-dev: Bump jest from 30.0.5 to 30.1.3 (#2655) (9a67555) - (dependabot[bot])
  • deps: Bump tj-actions/git-cliff from 2.1.0 to 2.2.0 (#2660) (b67e30d) - (dependabot[bot])
  • deps: Bump github/codeql-action from 3.30.2 to 3.30.3 (#2661) (62aef42) - (dependabot[bot])
  • deps: Bump github/codeql-action from 3.29.11 to 3.30.2 (#2659) (e874f3c) - (dependabot[bot])
  • deps: Bump actions/setup-node from 4.4.0 to 5.0.0 (#2656) (8c14441) - (dependabot[bot])
  • deps-dev: Bump @​types/node from 24.3.0 to 24.3.1 (#2657) (e995ac4) - (dependabot[bot])
  • deps-dev: Bump @​types/node from 24.2.1 to 24.3.0 (#2649) (3b04099) - (dependabot[bot])
  • deps: Bump github/codeql-action from 3.29.9 to 3.29.11 (#2651) (e7b6c97) - (dependabot[bot])
  • deps: Bump tj-actions/git-cliff from 2.0.2 to 2.1.0 (#2648) (765d62b) - (dependabot[bot])
  • deps: Bump github/codeql-action from 3.29.8 to 3.29.9 (#2647) (2036da1) - (dependabot[bot])
  • deps: Bump github/codeql-action from 3.29.7 to 3.29.8 (#2644) (239aef8) - (dependabot[bot])
  • deps-dev: Bump @​types/node from 24.2.0 to 24.2.1 (#2645) (a7d5f5f) - (dependabot[bot])
  • deps: Bump actions/checkout from 4.2.2 to 5.0.0 (#2646) (5107f3a) - (dependabot[bot])
  • deps-dev: Bump @​types/node from 24.1.0 to 24.2.0 (#2640) (f963b3f) - (dependabot[bot])
  • deps: Bump actions/download-artifact from 4.3.0 to 5.0.0 (#2641) (f956744) - (dependabot[bot])

... (truncated)

Commits
  • 0ff001d chore(deps-dev): bump ts-jest from 29.4.4 to 29.4.5 (#2688)
  • 52b808a chore(deps-dev): bump @​types/micromatch from 4.0.9 to 4.0.10 (#2699)
  • d6388b7 chore(deps): bump actions/download-artifact from 5.0.0 to 6.0.0 (#2697)
  • cf5e80a chore(deps): bump actions/upload-artifact from 4.6.2 to 5.0.0 (#2698)
  • cff4543 chore(deps-dev): bump @​types/node from 24.9.1 to 24.9.2 (#2700)
  • 9dc1b5f chore(deps): bump github/codeql-action from 4.30.9 to 4.31.2 (#2702)
  • See full diff in compare view

Updates `github/codeql-action` from 4.31.0 to 4.31.2
Release notes

Sourced from github/codeql-action's releases.

v4.31.2

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

4.31.2 - 30 Oct 2025

No user facing changes.

See the full CHANGELOG.md for more information.

v4.31.1

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

4.31.1 - 30 Oct 2025

  • The add-snippets input has been removed from the analyze action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced.

See the full CHANGELOG.md for more information.

Changelog

Sourced from github/codeql-action's changelog.

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

[UNRELEASED]

No user facing changes.

4.31.2 - 30 Oct 2025

No user facing changes.

4.31.1 - 30 Oct 2025

  • The add-snippets input has been removed from the analyze action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced.

4.31.0 - 24 Oct 2025

  • Bump minimum CodeQL bundle version to 2.17.6. #3223
  • When SARIF files are uploaded by the analyze or upload-sarif actions, the CodeQL Action automatically performs post-processing steps to prepare the data for the upload. Previously, these post-processing steps were only performed before an upload took place. We are now changing this so that the post-processing steps will always be performed, even when the SARIF files are not uploaded. This does not change anything for the upload-sarif action. For analyze, this may affect Advanced Setup for CodeQL users who specify a value other than always for the upload input. #3222

4.30.9 - 17 Oct 2025

  • Update default CodeQL bundle version to 2.23.3. #3205
  • Experimental: A new setup-codeql action has been added which is similar to init, except it only installs the CodeQL CLI and does not initialize a database. Do not use this in production as it is part of an internal experiment and subject to change at any time. #3204

4.30.8 - 10 Oct 2025

No user facing changes.

4.30.7 - 06 Oct 2025

  • [v4+ only] The CodeQL Action now runs on Node.js v24. #3169

3.30.6 - 02 Oct 2025

  • Update default CodeQL bundle version to 2.23.2. #3168

3.30.5 - 26 Sep 2025

  • We fixed a bug that was introduced in 3.30.4 with upload-sarif which resulted in files without a .sarif extension not getting uploaded. #3160

3.30.4 - 25 Sep 2025

  • We have improved the CodeQL Action's ability to validate that the workflow it is used in does not use different versions of the CodeQL Action for different workflow steps. Mixing different versions of the CodeQL Action in the same workflow is unsupported and can lead to unpredictable results. A warning will now be emitted from the codeql-action/init step if different versions of the CodeQL Action are detected in the workflow file. Additionally, an error will now be thrown by the other CodeQL Action steps if they load a configuration file that was generated by a different version of the codeql-action/init step. #3099 and #3100
  • We added support for reducing the size of dependency caches for Java analyses, which will reduce cache usage and speed up workflows. This will be enabled automatically at a later time. #3107
  • You can now run the latest CodeQL nightly bundle by passing tools: nightly to the init action. In general, the nightly bundle is unstable and we only recommend running it when directed by GitHub staff. #3130
  • Update default CodeQL bundle version to 2.23.1. #3118

3.30.3 - 10 Sep 2025

... (truncated)

Commits
  • 0499de3 Merge pull request #3261 from github/henrymercer/setup-python
  • 3b96745 Set up Python in mergeback workflow
  • 8a06050 Merge pull request #3259 from github/update-v4.31.2-9576b5cbe
  • 752a642 Update changelog for v4.31.2
  • 9576b5c Merge pull request #3258 from github/mbg/enablement-errors/case-insensitive
  • cc88437 Merge pull request #3257 from github/henrymercer/ubuntu-slim
  • f0e9bf0 Make isEnablementError case-insensitive
  • 2a3599c Run lightweight workflows on ubuntu-slim
  • 514ff4d Merge pull request #3256 from github/henrymercer/resolve-bad-merge
  • aab1c2f Merge pull request #3253 from github/mergeback/v4.31.1-to-main-5fe9434c
  • Additional commits viewable in compare view

Updates `Mattraks/delete-workflow-runs` from ab482449ba468316e9a8801e092d0405715c5e6d to 86d29a75093353c4c509a876c176234037c2025b
Commits
  • 86d29a7 Update deprecated @​octokit/rest library call methods
  • See full diff in compare view

Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | crate-ci/typos | [>= 1.30.a, < 1.31] |
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 6 +++--- .github/workflows/deploy.yaml | 2 +- .github/workflows/docs-ci.yaml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/security.yaml | 6 +++--- .github/workflows/stale.yaml | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 50e7d2d542bdf..7e146bfe87ad0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -191,7 +191,7 @@ jobs: # Check for any typos - name: Check for typos - uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1.38.1 + uses: crate-ci/typos@07d900b8fa1097806b8adb6391b0d3e0ac2fdea7 # v1.39.0 with: config: .github/workflows/typos.toml @@ -794,7 +794,7 @@ jobs: # the check to pass. This is desired in PRs, but not in mainline. - name: Publish to Chromatic (non-mainline) if: github.ref != 'refs/heads/main' && github.repository_owner == 'coder' - uses: chromaui/action@bc2d84ad2b60813a67d995c5582d696104a19383 # v13.3.2 + uses: chromaui/action@ac86f2ff0a458ffbce7b40698abd44c0fa34d4b6 # v13.3.3 env: NODE_OPTIONS: "--max_old_space_size=4096" STORYBOOK: true @@ -826,7 +826,7 @@ jobs: # infinitely "in progress" in mainline unless we re-review each build. - name: Publish to Chromatic (mainline) if: github.ref == 'refs/heads/main' && github.repository_owner == 'coder' - uses: chromaui/action@bc2d84ad2b60813a67d995c5582d696104a19383 # v13.3.2 + uses: chromaui/action@ac86f2ff0a458ffbce7b40698abd44c0fa34d4b6 # v13.3.3 env: NODE_OPTIONS: "--max_old_space_size=4096" STORYBOOK: true diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 6ea750a11caac..7e3d91a7d58f2 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -92,7 +92,7 @@ jobs: uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 - name: Set up Flux CLI - uses: fluxcd/flux2/action@4a15fa6a023259353ef750acf1c98fe88407d4d0 # v2.7.2 + uses: fluxcd/flux2/action@b6e76ca2534f76dcb8dd94fb057cdfa923c3b641 # v2.7.3 with: # Keep this and the github action up to date with the version of flux installed in dogfood cluster version: "2.7.0" diff --git a/.github/workflows/docs-ci.yaml b/.github/workflows/docs-ci.yaml index 749bdce9b25c3..9e46215c8a294 100644 --- a/.github/workflows/docs-ci.yaml +++ b/.github/workflows/docs-ci.yaml @@ -30,7 +30,7 @@ jobs: - name: Setup Node uses: ./.github/actions/setup-node - - uses: tj-actions/changed-files@dbf178ceecb9304128c8e0648591d71208c6e2c9 # v45.0.7 + - uses: tj-actions/changed-files@0ff001de0805038ff3f118de4875002200057732 # v45.0.7 id: changed-files with: files: | diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index c18b2d09a8233..0d3e56bfe811a 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -47,6 +47,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 with: sarif_file: results.sarif diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml index 21452b0b89f6f..e0c6d95456931 100644 --- a/.github/workflows/security.yaml +++ b/.github/workflows/security.yaml @@ -40,7 +40,7 @@ jobs: uses: ./.github/actions/setup-go - name: Initialize CodeQL - uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 with: languages: go, javascript @@ -50,7 +50,7 @@ jobs: rm Makefile - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 - name: Send Slack notification on failure if: ${{ failure() }} @@ -154,7 +154,7 @@ jobs: severity: "CRITICAL,HIGH" - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 with: sarif_file: trivy-results.sarif category: "Trivy" diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 75fb201bd5753..d459f5d5aeaeb 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -125,7 +125,7 @@ jobs: egress-policy: audit - name: Delete PR Cleanup workflow runs - uses: Mattraks/delete-workflow-runs@ab482449ba468316e9a8801e092d0405715c5e6d # v2.1.0 + uses: Mattraks/delete-workflow-runs@86d29a75093353c4c509a876c176234037c2025b # v2.1.0 with: token: ${{ github.token }} repository: ${{ github.repository }} @@ -134,7 +134,7 @@ jobs: delete_workflow_pattern: pr-cleanup.yaml - name: Delete PR Deploy workflow skipped runs - uses: Mattraks/delete-workflow-runs@ab482449ba468316e9a8801e092d0405715c5e6d # v2.1.0 + uses: Mattraks/delete-workflow-runs@86d29a75093353c4c509a876c176234037c2025b # v2.1.0 with: token: ${{ github.token }} repository: ${{ github.repository }} From 4fa5e6ee3b653276c92441afa5e83686c4af5222 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:26:01 +0000 Subject: [PATCH 082/255] chore: bump ubuntu from `4e0171b` to `0950623` in /dogfood/coder (#20658) Bumps ubuntu from `4e0171b` to `0950623`. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ubuntu&package-manager=docker&previous-version=jammy&new-version=jammy)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dogfood/coder/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dogfood/coder/Dockerfile b/dogfood/coder/Dockerfile index df668f7c8fb6e..ff55c56bf41db 100644 --- a/dogfood/coder/Dockerfile +++ b/dogfood/coder/Dockerfile @@ -8,7 +8,7 @@ RUN sed -i 's|http://deb.debian.org/debian|http://mirrors.edge.kernel.org/debian RUN apt-get update && apt-get install -y libssl-dev openssl pkg-config build-essential RUN cargo install jj-cli typos-cli watchexec-cli -FROM ubuntu:jammy@sha256:4e0171b9275e12d375863f2b3ae9ce00a4c53ddda176bd55868df97ac6f21a6e AS go +FROM ubuntu:jammy@sha256:09506232a8004baa32c47d68f1e5c307d648fdd59f5e7eaa42aaf87914100db3 AS go # Install Go manually, so that we can control the version ARG GO_VERSION=1.24.6 @@ -102,7 +102,7 @@ RUN curl -L -o protoc.zip https://github.com/protocolbuffers/protobuf/releases/d unzip protoc.zip && \ rm protoc.zip -FROM ubuntu:jammy@sha256:4e0171b9275e12d375863f2b3ae9ce00a4c53ddda176bd55868df97ac6f21a6e +FROM ubuntu:jammy@sha256:09506232a8004baa32c47d68f1e5c307d648fdd59f5e7eaa42aaf87914100db3 SHELL ["/bin/bash", "-c"] From afd7fc8687d11ea34f2b807a60404018012274af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:26:16 +0000 Subject: [PATCH 083/255] chore: bump rust from `e4ae8ab` to `af95fd1` in /dogfood/coder (#20657) Bumps rust from `e4ae8ab` to `af95fd1`. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rust&package-manager=docker&previous-version=slim&new-version=slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dogfood/coder/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dogfood/coder/Dockerfile b/dogfood/coder/Dockerfile index ff55c56bf41db..4fc886728edbc 100644 --- a/dogfood/coder/Dockerfile +++ b/dogfood/coder/Dockerfile @@ -1,5 +1,5 @@ # 1.86.0 -FROM rust:slim@sha256:e4ae8ab67883487c5545884d5aa5ebbe86b5f13c6df4a8e3e2f34c89cedb9f54 AS rust-utils +FROM rust:slim@sha256:af95fd1bb203d15e0e82a3c2ade1799767aa99dd91a652ce044533d6582d7415 AS rust-utils # Install rust helper programs ENV CARGO_INSTALL_ROOT=/tmp/ # Use more reliable mirrors for Debian packages From daad93967ac6c40b6335cdf1108dcf2036e6cc99 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 3 Nov 2025 14:54:43 +0200 Subject: [PATCH 084/255] fix(coderd): fix template ai task check error message (#20651) Create task was still mentioning magic prompt parameter when checking template task validity. This change updates it to only mention validity of `coder_ai_task` resource. --- coderd/aitasks.go | 2 +- codersdk/toolsdk/toolsdk_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/aitasks.go b/coderd/aitasks.go index 9611785b853f5..d5cca9e4f0b3f 100644 --- a/coderd/aitasks.go +++ b/coderd/aitasks.go @@ -92,7 +92,7 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) { if !templateVersion.HasAITask.Valid || !templateVersion.HasAITask.Bool { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: fmt.Sprintf(`Template does not have required parameter %q`, codersdk.AITaskPromptParameterName), + Message: `Template does not have a valid "coder_ai_task" resource.`, }) return } diff --git a/codersdk/toolsdk/toolsdk_test.go b/codersdk/toolsdk/toolsdk_test.go index acd7dba8c09d7..a5d94e4306ad6 100644 --- a/codersdk/toolsdk/toolsdk_test.go +++ b/codersdk/toolsdk/toolsdk_test.go @@ -871,7 +871,7 @@ func TestTools(t *testing.T) { TemplateVersionID: r.TemplateVersion.ID.String(), Input: "do yet another barrel roll", }, - error: "Template does not have required parameter \"AI Prompt\"", + error: "Template does not have a valid \"coder_ai_task\" resource.", }, { name: "WithPreset", @@ -880,7 +880,7 @@ func TestTools(t *testing.T) { TemplateVersionPresetID: presetID.String(), Input: "not enough barrel rolls", }, - error: "Template does not have required parameter \"AI Prompt\"", + error: "Template does not have a valid \"coder_ai_task\" resource.", }, } From b3f651d62f27a762d8f864b6123d111fce4626c0 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Wed, 5 Nov 2025 00:46:27 +1100 Subject: [PATCH 085/255] chore: change managed agent limit (#20540) --- enterprise/coderd/license/license.go | 57 ++++++++++++----------- enterprise/coderd/license/license_test.go | 28 +++++------ 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/enterprise/coderd/license/license.go b/enterprise/coderd/license/license.go index e66915f221f5a..3cf23823d2d5d 100644 --- a/enterprise/coderd/license/license.go +++ b/enterprise/coderd/license/license.go @@ -262,6 +262,36 @@ func LicensesEntitlements( claims.FeatureSet = codersdk.FeatureSetEnterprise } + // Temporary: If the license doesn't have a managed agent limit, we add + // a default of 1000 managed agents per deployment for a 100 + // year license term. + // This only applies to "Premium" licenses. + if claims.FeatureSet == codersdk.FeatureSetPremium { + var ( + // We intentionally use a fixed issue time here, before the + // entitlement was added to any new licenses, so any + // licenses with the corresponding features actually set + // trump this default entitlement, even if they are set to a + // smaller value. + defaultManagedAgentsIsuedAt = time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC) + defaultManagedAgentsStart = defaultManagedAgentsIsuedAt + defaultManagedAgentsEnd = defaultManagedAgentsStart.AddDate(100, 0, 0) + defaultManagedAgentsSoftLimit int64 = 1000 + defaultManagedAgentsHardLimit int64 = 1000 + ) + entitlements.AddFeature(codersdk.FeatureManagedAgentLimit, codersdk.Feature{ + Enabled: true, + Entitlement: entitlement, + SoftLimit: &defaultManagedAgentsSoftLimit, + Limit: &defaultManagedAgentsHardLimit, + UsagePeriod: &codersdk.UsagePeriod{ + IssuedAt: defaultManagedAgentsIsuedAt, + Start: defaultManagedAgentsStart, + End: defaultManagedAgentsEnd, + }, + }) + } + // Add all features from the feature set defined. for _, featureName := range claims.FeatureSet.Features() { if _, ok := licenseForbiddenFeatures[featureName]; ok { @@ -338,33 +368,6 @@ func LicensesEntitlements( Limit: &featureValue, Actual: &featureArguments.ActiveUserCount, }) - - // Temporary: If the license doesn't have a managed agent limit, - // we add a default of 800 managed agents per user. - // This only applies to "Premium" licenses. - if claims.FeatureSet == codersdk.FeatureSetPremium { - var ( - // We intentionally use a fixed issue time here, before the - // entitlement was added to any new licenses, so any - // licenses with the corresponding features actually set - // trump this default entitlement, even if they are set to a - // smaller value. - issueTime = time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC) - defaultSoftAgentLimit = 800 * featureValue - defaultHardAgentLimit = 1000 * featureValue - ) - entitlements.AddFeature(codersdk.FeatureManagedAgentLimit, codersdk.Feature{ - Enabled: true, - Entitlement: entitlement, - SoftLimit: &defaultSoftAgentLimit, - Limit: &defaultHardAgentLimit, - UsagePeriod: &codersdk.UsagePeriod{ - IssuedAt: issueTime, - Start: usagePeriodStart, - End: usagePeriodEnd, - }, - }) - } default: if featureValue <= 0 { // The feature is disabled. diff --git a/enterprise/coderd/license/license_test.go b/enterprise/coderd/license/license_test.go index 0e540989b69da..6c53fb3d89f22 100644 --- a/enterprise/coderd/license/license_test.go +++ b/enterprise/coderd/license/license_test.go @@ -520,8 +520,8 @@ func TestEntitlements(t *testing.T) { t.Run("Premium", func(t *testing.T) { t.Parallel() const userLimit = 1 - const expectedAgentSoftLimit = 800 * userLimit - const expectedAgentHardLimit = 1000 * userLimit + const expectedAgentSoftLimit = 1000 + const expectedAgentHardLimit = 1000 db, _ := dbtestutil.NewDB(t) licenseOptions := coderdenttest.LicenseOptions{ @@ -530,9 +530,7 @@ func TestEntitlements(t *testing.T) { ExpiresAt: dbtime.Now().Add(time.Hour * 24 * 2), FeatureSet: codersdk.FeatureSetPremium, Features: license.Features{ - // Temporary: allows the default value for the - // managed_agent_limit feature to be used. - codersdk.FeatureUserLimit: 1, + codersdk.FeatureUserLimit: userLimit, }, } _, err := db.InsertLicense(context.Background(), database.InsertLicenseParams{ @@ -557,11 +555,15 @@ func TestEntitlements(t *testing.T) { require.Equal(t, codersdk.EntitlementEntitled, agentEntitlement.Entitlement) require.EqualValues(t, expectedAgentSoftLimit, *agentEntitlement.SoftLimit) require.EqualValues(t, expectedAgentHardLimit, *agentEntitlement.Limit) + // This might be shocking, but there's a sound reason for this. // See license.go for more details. - require.Equal(t, time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC), agentEntitlement.UsagePeriod.IssuedAt) - require.WithinDuration(t, licenseOptions.NotBefore, agentEntitlement.UsagePeriod.Start, time.Second) - require.WithinDuration(t, licenseOptions.ExpiresAt, agentEntitlement.UsagePeriod.End, time.Second) + agentUsagePeriodIssuedAt := time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC) + agentUsagePeriodStart := agentUsagePeriodIssuedAt + agentUsagePeriodEnd := agentUsagePeriodStart.AddDate(100, 0, 0) + require.Equal(t, agentUsagePeriodIssuedAt, agentEntitlement.UsagePeriod.IssuedAt) + require.WithinDuration(t, agentUsagePeriodStart, agentEntitlement.UsagePeriod.Start, time.Second) + require.WithinDuration(t, agentUsagePeriodEnd, agentEntitlement.UsagePeriod.End, time.Second) continue } @@ -1496,14 +1498,14 @@ func TestManagedAgentLimitDefault(t *testing.T) { }) // "Premium" licenses should receive a default managed agent limit of: - // soft = 800 * user_limit - // hard = 1000 * user_limit + // soft = 1000 + // hard = 1000 t.Run("Premium", func(t *testing.T) { t.Parallel() - const userLimit = 100 - const softLimit = 800 * userLimit - const hardLimit = 1000 * userLimit + const userLimit = 33 + const softLimit = 1000 + const hardLimit = 1000 lic := database.License{ ID: 1, UploadedAt: time.Now(), From dec2c4c4e2e37cab2668b7f3a3fc27c642cef112 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 4 Nov 2025 16:16:43 +0200 Subject: [PATCH 086/255] fix(site): fix task table links in Safari (#20663) This change makes the TableRow clickable rather than having an absolutely positioned RouterLink. In Safari the entire table was broken because all links spanned across the whole table, and the bottom most row shadowed all others, resulting in only the bottom row being highlighted and all rows leading to the bottom most task. --- site/src/pages/TasksPage/TasksTable.tsx | 36 ++++++++++++++----------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/site/src/pages/TasksPage/TasksTable.tsx b/site/src/pages/TasksPage/TasksTable.tsx index be0700c815dc1..028386d243e38 100644 --- a/site/src/pages/TasksPage/TasksTable.tsx +++ b/site/src/pages/TasksPage/TasksTable.tsx @@ -23,11 +23,12 @@ import { TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; +import { useClickableTableRow } from "hooks"; import { RotateCcwIcon, TrashIcon } from "lucide-react"; import { TaskDeleteDialog } from "modules/tasks/TaskDeleteDialog/TaskDeleteDialog"; import { TaskStatus } from "modules/tasks/TaskStatus/TaskStatus"; import { type FC, type ReactNode, useState } from "react"; -import { Link as RouterLink } from "react-router"; +import { useNavigate } from "react-router"; import { relativeTime } from "utils/time"; type TasksTableProps = { @@ -116,24 +117,27 @@ type TaskRowProps = { task: Task }; const TaskRow: FC = ({ task }) => { const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const templateDisplayName = task.template_display_name ?? task.template_name; + const navigate = useNavigate(); + + const taskPageLink = `/tasks/${task.owner_name}/${task.id}`; + // Discard role, breaks Chromatic. + const { role, ...clickableRowProps } = useClickableTableRow({ + onClick: () => navigate(taskPageLink), + }); return ( <> - + - - {task.initial_prompt} - - - Access task - - + + {task.initial_prompt} + } subtitle={templateDisplayName} avatar={ @@ -171,8 +175,10 @@ const TaskRow: FC = ({ task }) => {
diff --git a/site/src/modules/permissions/index.ts b/site/src/modules/permissions/index.ts index db48e61411d18..57eff5f67f5a5 100644 --- a/site/src/modules/permissions/index.ts +++ b/site/src/modules/permissions/index.ts @@ -169,6 +169,13 @@ export const permissionChecks = { }, action: "read", }, + viewAnyAIBridgeInterception: { + object: { + resource_type: "aibridge_interception", + any_org: true, + }, + action: "read", + }, } as const satisfies Record; export const canViewDeploymentSettings = ( diff --git a/site/src/pages/AIGovernancePage/AIGovernanceHelpTooltip.tsx b/site/src/pages/AIGovernancePage/AIGovernanceHelpTooltip.tsx new file mode 100644 index 0000000000000..e8dd2a6b77b20 --- /dev/null +++ b/site/src/pages/AIGovernancePage/AIGovernanceHelpTooltip.tsx @@ -0,0 +1,32 @@ +import { + HelpTooltip, + HelpTooltipContent, + HelpTooltipIconTrigger, + HelpTooltipLink, + HelpTooltipLinksGroup, + HelpTooltipText, + HelpTooltipTitle, +} from "components/HelpTooltip/HelpTooltip"; +import type { FC } from "react"; +import { docs } from "utils/docs"; + +export const AIGovernanceHelpTooltip: FC = () => { + return ( + + + + + What is AI Governance? + + AI Governance is a proxy that unifies and audits LLM usage across your + organization. + + + + What we track + + + + + ); +}; diff --git a/site/src/pages/AIGovernancePage/AIGovernanceLayout.tsx b/site/src/pages/AIGovernancePage/AIGovernanceLayout.tsx new file mode 100644 index 0000000000000..bf8a0e6544b0f --- /dev/null +++ b/site/src/pages/AIGovernancePage/AIGovernanceLayout.tsx @@ -0,0 +1,30 @@ +import { Margins } from "components/Margins/Margins"; +import { + PageHeader, + PageHeaderSubtitle, + PageHeaderTitle, +} from "components/PageHeader/PageHeader"; +import type { FC, PropsWithChildren } from "react"; +import { Outlet } from "react-router"; +import { AIGovernanceHelpTooltip } from "./AIGovernanceHelpTooltip"; + +const AIGovernanceLayout: FC = () => { + return ( + + + +
+ AI Governance + +
+
+ + Manage usage for your organization. + +
+ +
+ ); +}; + +export default AIGovernanceLayout; diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPage.tsx b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPage.tsx new file mode 100644 index 0000000000000..2f56ff476091a --- /dev/null +++ b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPage.tsx @@ -0,0 +1,66 @@ +import { paginatedInterceptions } from "api/queries/aiBridge"; +import { useFilter } from "components/Filter/Filter"; +import { useUserFilterMenu } from "components/Filter/UserFilter"; +import { usePaginatedQuery } from "hooks/usePaginatedQuery"; +import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; +import type { FC } from "react"; +import { useSearchParams } from "react-router"; +import { pageTitle } from "utils/page"; +import { useProviderFilterMenu } from "./filter/filter"; +import { RequestLogsPageView } from "./RequestLogsPageView"; + +const RequestLogsPage: FC = () => { + const feats = useFeatureVisibility(); + const isRequestLogsVisible = Boolean(feats.aibridge); + + const [searchParams, setSearchParams] = useSearchParams(); + const interceptionsQuery = usePaginatedQuery( + paginatedInterceptions(searchParams), + ); + const filter = useFilter({ + searchParams, + onSearchParamsChange: setSearchParams, + onUpdate: interceptionsQuery.goToFirstPage, + }); + + const userMenu = useUserFilterMenu({ + value: filter.values.initiator, + onChange: (option) => + filter.update({ + ...filter.values, + initiator: option?.value, + }), + }); + + const providerMenu = useProviderFilterMenu({ + value: filter.values.provider, + onChange: (option) => + filter.update({ + ...filter.values, + provider: option?.value, + }), + }); + + return ( + <> + {pageTitle("Request Logs", "AI Governance")} + + + + ); +}; + +export default RequestLogsPage; diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPageView.stories.tsx b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPageView.stories.tsx new file mode 100644 index 0000000000000..3899eb08d06b4 --- /dev/null +++ b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPageView.stories.tsx @@ -0,0 +1,77 @@ +import { MockInterception } from "testHelpers/entities"; +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { + getDefaultFilterProps, + MockMenu, +} from "components/Filter/storyHelpers"; +import { + mockInitialRenderResult, + mockSuccessResult, +} from "components/PaginationWidget/PaginationContainer.mocks"; +import type { ComponentProps } from "react"; +import { RequestLogsPageView } from "./RequestLogsPageView"; + +type FilterProps = ComponentProps["filterProps"]; + +const defaultFilterProps = getDefaultFilterProps({ + query: "owner:me", + values: { + username: undefined, + provider: undefined, + }, + menus: { + user: MockMenu, + provider: MockMenu, + }, +}); + +const interceptions = [MockInterception, MockInterception, MockInterception]; + +const meta: Meta = { + title: "pages/AIGovernancePage/RequestLogsPageView", + component: RequestLogsPageView, + args: {}, +}; + +export default meta; +type Story = StoryObj; + +export const Paywall: Story = { + args: { + isRequestLogsVisible: false, + }, +}; + +export const Loaded: Story = { + args: { + isRequestLogsVisible: true, + interceptions, + filterProps: { + ...defaultFilterProps, + }, + interceptionsQuery: mockSuccessResult, + }, +}; + +export const Empty: Story = { + args: { + isRequestLogsVisible: true, + interceptions: [], + filterProps: { + ...defaultFilterProps, + }, + interceptionsQuery: mockSuccessResult, + }, +}; + +export const Loading: Story = { + args: { + isLoading: true, + isRequestLogsVisible: true, + interceptions: [], + filterProps: { + ...defaultFilterProps, + }, + interceptionsQuery: mockInitialRenderResult, + }, +}; diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPageView.tsx b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPageView.tsx new file mode 100644 index 0000000000000..8592206bcdf33 --- /dev/null +++ b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPageView.tsx @@ -0,0 +1,82 @@ +import type { AIBridgeInterception } from "api/typesGenerated"; +import { + PaginationContainer, + type PaginationResult, +} from "components/PaginationWidget/PaginationContainer"; +import { Paywall } from "components/Paywall/Paywall"; +import { + Table, + TableBody, + TableHead, + TableHeader, + TableRow, +} from "components/Table/Table"; +import { TableEmpty } from "components/TableEmpty/TableEmpty"; +import { TableLoader } from "components/TableLoader/TableLoader"; +import type { ComponentProps, FC } from "react"; +import { docs } from "utils/docs"; +import { RequestLogsFilter } from "./filter/RequestLogsFilter"; +import { RequestLogsRow } from "./RequestLogsRow/RequestLogsRow"; + +interface RequestLogsPageViewProps { + isLoading: boolean; + isRequestLogsVisible: boolean; + interceptions?: readonly AIBridgeInterception[]; + interceptionsQuery: PaginationResult; + filterProps: ComponentProps; +} + +export const RequestLogsPageView: FC = ({ + isLoading, + isRequestLogsVisible, + interceptions, + interceptionsQuery, + filterProps, +}) => { + if (!isRequestLogsVisible) { + return ( + + ); + } + + return ( + <> + + + + + + + Timestamp + User + Prompt + Tokens + Tool Calls + + + + {isLoading ? ( + + ) : interceptions?.length === 0 ? ( + + ) : ( + interceptions?.map((interception) => ( + + )) + )} + +
+
+ + ); +}; diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx new file mode 100644 index 0000000000000..b4067ee3c9cb3 --- /dev/null +++ b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx @@ -0,0 +1,27 @@ +import { MockInterception } from "testHelpers/entities"; +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { Table, TableBody } from "components/Table/Table"; +import { RequestLogsRow } from "./RequestLogsRow"; + +const meta: Meta = { + title: "pages/AIGovernancePage/RequestLogsRow", + component: RequestLogsRow, + decorators: [ + (Story) => ( + + + + +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Close: Story = { + args: { + interception: MockInterception, + }, +}; diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx new file mode 100644 index 0000000000000..06f821ef88cf7 --- /dev/null +++ b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx @@ -0,0 +1,200 @@ +import type { AIBridgeInterception } from "api/typesGenerated"; +import { Avatar } from "components/Avatar/Avatar"; +import { TableCell, TableRow } from "components/Table/Table"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; +import { + ArrowDownIcon, + ArrowUpIcon, + ChevronDownIcon, + ChevronRightIcon, +} from "lucide-react"; +import { type FC, Fragment, useState } from "react"; +import { cn } from "utils/cn"; + +type RequestLogsRowProps = { + interception: AIBridgeInterception; +}; + +export const RequestLogsRow: FC = ({ interception }) => { + const [isOpen, setIsOpen] = useState(false); + + const [firstPrompt] = interception.user_prompts; + + const inputTokens = interception.token_usages.reduce( + (acc, tokenUsage) => acc + tokenUsage.input_tokens, + 0, + ); + const outputTokens = interception.token_usages.reduce( + (acc, tokenUsage) => acc + tokenUsage.output_tokens, + 0, + ); + const toolCalls = interception.tool_usages.length; + + return ( + <> + setIsOpen(!isOpen)} + > + +
+ {isOpen ? ( + + ) : ( + + )} + ({isOpen ? "Hide" : "Show more"}) + {new Date(interception.started_at).toLocaleString()} +
+
+ +
+ +
{interception.initiator.username}
+
+
+ {firstPrompt?.prompt} + +
+ + + +
+ +
{inputTokens}
+
+
+ Input Tokens +
+
+ + + +
+ +
{outputTokens}
+
+
+ Output Tokens +
+
+
+
+ {toolCalls} +
+ {isOpen && ( + + +
+
+
Request ID:
+
{interception.id}
+ +
Start Time:
+
+ {new Date(interception.started_at).toLocaleString()} +
+ + {interception.ended_at && ( + <> +
End Time:
+
+ {new Date(interception.ended_at).toLocaleString()} +
+ + )} + +
Initiator:
+
+ {interception.initiator.username} +
+ +
Model:
+
{interception.model}
+ +
Input Tokens:
+
{inputTokens}
+ +
Output Tokens:
+
{outputTokens}
+ +
Tool Calls:
+
+ {interception.tool_usages.length} +
+
+ + {interception.user_prompts.length > 0 && ( +
+
Prompts
+
+ {interception.user_prompts.map((prompt) => ( + {prompt.prompt} + ))} +
+
+ )} + + {interception.tool_usages.length > 0 && ( +
+
Tool Usages
+
+ {interception.tool_usages.map((toolUsage) => { + return ( +
+
{toolUsage.tool}
+
+
+
{toolUsage.input}
+ {toolUsage.invocation_error && ( +
+ {toolUsage.invocation_error} +
+ )} +
+
+
+ ); + })} +
+
+ )} +
+
+
+ )} + + ); +}; diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/filter/RequestLogsFilter.tsx b/site/src/pages/AIGovernancePage/RequestLogsPage/filter/RequestLogsFilter.tsx new file mode 100644 index 0000000000000..afc5c969fb2d0 --- /dev/null +++ b/site/src/pages/AIGovernancePage/RequestLogsPage/filter/RequestLogsFilter.tsx @@ -0,0 +1,44 @@ +import { Filter, MenuSkeleton, type useFilter } from "components/Filter/Filter"; +import { type UserFilterMenu, UserMenu } from "components/Filter/UserFilter"; +import type { FC } from "react"; +import { ProviderFilter, type ProviderFilterMenu } from "./filter"; + +interface RequestLogsFilterProps { + filter: ReturnType; + error?: unknown; + menus: { + user: UserFilterMenu; + provider: ProviderFilterMenu; + }; +} + +export const RequestLogsFilter: FC = ({ + filter, + error, + menus, +}) => { + return ( + } + isLoading={menus.user.isInitializing} + presets={[ + { + name: "All requests", + query: "", + }, + { + name: "My requests", + query: "initiator:me", + }, + ]} + error={error} + options={ + <> + + + + } + /> + ); +}; diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/filter/filter.tsx b/site/src/pages/AIGovernancePage/RequestLogsPage/filter/filter.tsx new file mode 100644 index 0000000000000..3184bbe4417e7 --- /dev/null +++ b/site/src/pages/AIGovernancePage/RequestLogsPage/filter/filter.tsx @@ -0,0 +1,57 @@ +import { + type UseFilterMenuOptions, + useFilterMenu, +} from "components/Filter/menu"; +import { + SelectFilter, + type SelectFilterOption, +} from "components/Filter/SelectFilter"; +import type { FC } from "react"; + +const AIBRIDGE_PROVIDERS: SelectFilterOption[] = [ + { + label: "OpenAI", + value: "openai", + }, + { + label: "Anthropic", + value: "anthropic", + }, +]; + +export const useProviderFilterMenu = ({ + value, + onChange, + enabled, +}: Pick) => { + return useFilterMenu({ + id: "provider", + getSelectedOption: async () => + AIBRIDGE_PROVIDERS.find((option) => option.value === value) ?? null, + getOptions: async () => { + return AIBRIDGE_PROVIDERS; + }, + value, + onChange, + enabled, + }); +}; + +export type ProviderFilterMenu = ReturnType; + +interface ProviderFilterProps { + menu: ProviderFilterMenu; +} + +export const ProviderFilter: FC = ({ menu }) => { + return ( + menu.selectOption(option)} + selectedOption={menu.selectedOption ?? undefined} + /> + ); +}; diff --git a/site/src/router.tsx b/site/src/router.tsx index c0da37b6810ec..34b8cf82b53f4 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -334,6 +334,12 @@ const ProvisionerJobsPage = lazy( ); const TasksPage = lazy(() => import("./pages/TasksPage/TasksPage")); const TaskPage = lazy(() => import("./pages/TaskPage/TaskPage")); +const AIGovernanceLayout = lazy( + () => import("./pages/AIGovernancePage/AIGovernanceLayout"), +); +const AIGovernanceRequestLogsPage = lazy( + () => import("./pages/AIGovernancePage/RequestLogsPage/RequestLogsPage"), +); const RoutesWithSuspense = () => { return ( @@ -557,6 +563,14 @@ export const router = createBrowserRouter( + }> + } /> + } + /> + + }> } /> } /> diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index f858db66d1f20..03d108b859648 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -3101,6 +3101,7 @@ export const MockPermissions: Permissions = { editAnySettings: true, viewAnyIdpSyncSettings: true, viewAnyMembers: true, + viewAnyAIBridgeInterception: true, }; export const MockNoPermissions: Permissions = { @@ -3129,6 +3130,7 @@ export const MockNoPermissions: Permissions = { editAnySettings: false, viewAnyIdpSyncSettings: false, viewAnyMembers: false, + viewAnyAIBridgeInterception: true, }; export const MockOrganizationPermissions: OrganizationPermissions = { @@ -5051,3 +5053,31 @@ export const MockTasks = [ }, }, ] satisfies TypesGen.Task[]; + +export const MockInterception: TypesGen.AIBridgeInterception = { + id: "5c1da48a-9eb0-440e-9c82-5bc5692a603d", + initiator: { + id: "1ebb7622-e6ea-45b4-b244-dda30afc7238", + username: "testuser", + avatar_url: "https://example.com/avatar.png", + }, + provider: "openai", + model: "gpt-4o", + started_at: "2022-05-17T17:39:01.382927298Z", + ended_at: "2022-05-17T17:39:01.382927298Z", + token_usages: [ + { + id: "32e7fd17-24be-46b9-b867-2f0adfd42aff", + interception_id: "5c1da48a-9eb0-440e-9c82-5bc5692a603d", + provider_response_id: "res_1234567890", + input_tokens: 5, + output_tokens: 1, + metadata: {}, + created_at: "2022-05-17T17:39:01.382927298Z", + }, + ], + metadata: {}, + user_prompts: [], + tool_usages: [], + api_key_id: "5c1da48a-9eb0-440e-9c82-5bc5692a603d", +}; From 903c045b9c1175ebe6f9be55d2a431fd892cefab Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Wed, 12 Nov 2025 13:08:40 +1100 Subject: [PATCH 119/255] fix: retain `searchParams` in `paginatedInterceptions` during filter updates (#20725) This pull-request ensures that when we attempt to navigate in-between pages or actually attempt to update the filters that they will persist as we expect. This was also causing an issue where on first load (or where it lacked `page=1` to be specific) that these would seem to lose their input. --- site/src/api/queries/aiBridge.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/api/queries/aiBridge.ts b/site/src/api/queries/aiBridge.ts index ba623637e9333..1e385bc464564 100644 --- a/site/src/api/queries/aiBridge.ts +++ b/site/src/api/queries/aiBridge.ts @@ -7,6 +7,7 @@ export const paginatedInterceptions = ( searchParams: URLSearchParams, ): UsePaginatedQueryOptions => { return { + searchParams, queryPayload: () => searchParams.get(useFilterParamsKey) ?? "", queryKey: ({ payload, pageNumber }) => { return ["aiBridgeInterceptions", payload, pageNumber] as const; From e49c917bb0a885b1dc3b3f441573615b10b9f7e8 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Tue, 11 Nov 2025 21:23:23 -0500 Subject: [PATCH 120/255] perf: use a single query for notification target lookups (#20574) Somewhat minor inefficiency in notifications I discovered during a scaletest where I was creating many users. Our `GetUsers` query filter for rbac roles uses the `&&` operator on arrays, which is the intersection of the two arrays. Despite that, we were making seperate DB queries for each role, and then collating the results. I didn't see any other instances of this. The test changes are required as the order of outgoing notifications is now non-deterministic. --- coderd/templates.go | 14 +++------- coderd/users.go | 14 +++------- coderd/users_test.go | 62 ++++++++++++++++++++++++++++---------------- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/coderd/templates.go b/coderd/templates.go index 9202fc48234a6..c93d0debf06e7 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -1130,19 +1130,11 @@ func (api *API) convertTemplate( // findTemplateAdmins fetches all users with template admin permission including owners. func findTemplateAdmins(ctx context.Context, store database.Store) ([]database.GetUsersRow, error) { - // Notice: we can't scrape the user information in parallel as pq - // fails with: unexpected describe rows response: 'D' - owners, err := store.GetUsers(ctx, database.GetUsersParams{ - RbacRole: []string{codersdk.RoleOwner}, - }) - if err != nil { - return nil, xerrors.Errorf("get owners: %w", err) - } templateAdmins, err := store.GetUsers(ctx, database.GetUsersParams{ - RbacRole: []string{codersdk.RoleTemplateAdmin}, + RbacRole: []string{codersdk.RoleTemplateAdmin, codersdk.RoleOwner}, }) if err != nil { - return nil, xerrors.Errorf("get template admins: %w", err) + return nil, xerrors.Errorf("get owners: %w", err) } - return append(owners, templateAdmins...), nil + return templateAdmins, nil } diff --git a/coderd/users.go b/coderd/users.go index 30fa7bf7cabeb..3ac05ff768911 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -1537,21 +1537,13 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create // findUserAdmins fetches all users with user admin permission including owners. func findUserAdmins(ctx context.Context, store database.Store) ([]database.GetUsersRow, error) { - // Notice: we can't scrape the user information in parallel as pq - // fails with: unexpected describe rows response: 'D' - owners, err := store.GetUsers(ctx, database.GetUsersParams{ - RbacRole: []string{codersdk.RoleOwner}, - }) - if err != nil { - return nil, xerrors.Errorf("get owners: %w", err) - } userAdmins, err := store.GetUsers(ctx, database.GetUsersParams{ - RbacRole: []string{codersdk.RoleUserAdmin}, + RbacRole: []string{codersdk.RoleOwner, codersdk.RoleUserAdmin}, }) if err != nil { - return nil, xerrors.Errorf("get user admins: %w", err) + return nil, xerrors.Errorf("get owners: %w", err) } - return append(owners, userAdmins...), nil + return userAdmins, nil } func convertUsers(users []database.User, organizationIDsByUserID map[uuid.UUID][]uuid.UUID) []codersdk.User { diff --git a/coderd/users_test.go b/coderd/users_test.go index 283b607e89df9..b8d74272edd8b 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -599,21 +599,28 @@ func TestNotifyDeletedUser(t *testing.T) { // then sent := notifyEnq.Sent() require.Len(t, sent, 5) - // sent[0]: "User admin" account created, "owner" notified - // sent[1]: "Member" account created, "owner" notified - // sent[2]: "Member" account created, "user admin" notified + // Other notifications: + // "User admin" account created, "owner" notified + // "Member" account created, "owner" notified + // "Member" account created, "user admin" notified // "Member" account deleted, "owner" notified - require.Equal(t, notifications.TemplateUserAccountDeleted, sent[3].TemplateID) - require.Equal(t, firstUser.UserID, sent[3].UserID) - require.Contains(t, sent[3].Targets, member.ID) - require.Equal(t, member.Username, sent[3].Labels["deleted_account_name"]) + ownerNotifications := notifyEnq.Sent(func(n *notificationstest.FakeNotification) bool { + return n.TemplateID == notifications.TemplateUserAccountDeleted && + n.UserID == firstUser.UserID && + slices.Contains(n.Targets, member.ID) && + n.Labels["deleted_account_name"] == member.Username + }) + require.Len(t, ownerNotifications, 1) // "Member" account deleted, "user admin" notified - require.Equal(t, notifications.TemplateUserAccountDeleted, sent[4].TemplateID) - require.Equal(t, userAdmin.ID, sent[4].UserID) - require.Contains(t, sent[4].Targets, member.ID) - require.Equal(t, member.Username, sent[4].Labels["deleted_account_name"]) + adminNotifications := notifyEnq.Sent(func(n *notificationstest.FakeNotification) bool { + return n.TemplateID == notifications.TemplateUserAccountDeleted && + n.UserID == userAdmin.ID && + slices.Contains(n.Targets, member.ID) && + n.Labels["deleted_account_name"] == member.Username + }) + require.Len(t, adminNotifications, 1) }) } @@ -960,22 +967,31 @@ func TestNotifyCreatedUser(t *testing.T) { require.Len(t, sent, 3) // "User admin" account created, "owner" notified - require.Equal(t, notifications.TemplateUserAccountCreated, sent[0].TemplateID) - require.Equal(t, firstUser.UserID, sent[0].UserID) - require.Contains(t, sent[0].Targets, userAdmin.ID) - require.Equal(t, userAdmin.Username, sent[0].Labels["created_account_name"]) + ownerNotifiedAboutUserAdmin := notifyEnq.Sent(func(n *notificationstest.FakeNotification) bool { + return n.TemplateID == notifications.TemplateUserAccountCreated && + n.UserID == firstUser.UserID && + slices.Contains(n.Targets, userAdmin.ID) && + n.Labels["created_account_name"] == userAdmin.Username + }) + require.Len(t, ownerNotifiedAboutUserAdmin, 1) // "Member" account created, "owner" notified - require.Equal(t, notifications.TemplateUserAccountCreated, sent[1].TemplateID) - require.Equal(t, firstUser.UserID, sent[1].UserID) - require.Contains(t, sent[1].Targets, member.ID) - require.Equal(t, member.Username, sent[1].Labels["created_account_name"]) + ownerNotifiedAboutMember := notifyEnq.Sent(func(n *notificationstest.FakeNotification) bool { + return n.TemplateID == notifications.TemplateUserAccountCreated && + n.UserID == firstUser.UserID && + slices.Contains(n.Targets, member.ID) && + n.Labels["created_account_name"] == member.Username + }) + require.Len(t, ownerNotifiedAboutMember, 1) // "Member" account created, "user admin" notified - require.Equal(t, notifications.TemplateUserAccountCreated, sent[1].TemplateID) - require.Equal(t, userAdmin.ID, sent[2].UserID) - require.Contains(t, sent[2].Targets, member.ID) - require.Equal(t, member.Username, sent[2].Labels["created_account_name"]) + userAdminNotifiedAboutMember := notifyEnq.Sent(func(n *notificationstest.FakeNotification) bool { + return n.TemplateID == notifications.TemplateUserAccountCreated && + n.UserID == userAdmin.ID && + slices.Contains(n.Targets, member.ID) && + n.Labels["created_account_name"] == member.Username + }) + require.Len(t, userAdminNotifiedAboutMember, 1) }) } From f543a87b7871cbea2b9f23e1ac6a8a807608dafb Mon Sep 17 00:00:00 2001 From: Kacper Sawicki Date: Wed, 12 Nov 2025 09:43:22 +0100 Subject: [PATCH 121/255] chore: cache terraform providers for workspaces terraform tests (#20603) Fixes flaky `TestWorkspaceTagsTerraform` and `TestWorkspaceTemplateParamsChange` tests that were failing with `connection reset by peer` errors when downloading the coder/coder provider. This applies the same caching solution which was done in https://github.com/coder/coder/pull/17373 1. Extracts provider caching logic into `testutil/terraform_cache.go` 2. Updates TestProvision to use the shared caching helpers 3. Updates enterprise workspace tests to use the shared caching helpers The cache is persisted at `~/.cache/coderv2-test/` and automatically cached between CI runs via existing GitHub Actions cache setup. Closes https://github.com/coder/internal/issues/607 --- enterprise/coderd/workspaces_test.go | 52 ++----- provisioner/terraform/provision_test.go | 170 +--------------------- testutil/terraform_cache.go | 185 ++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 211 deletions(-) create mode 100644 testutil/terraform_cache.go diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 5201e613f7a1d..44244f238f113 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -7,8 +7,6 @@ import ( "encoding/json" "fmt" "net/http" - "os" - "os/exec" "path/filepath" "strings" "sync/atomic" @@ -3390,51 +3388,19 @@ func workspaceTagsTerraform(t *testing.T, tc testWorkspaceTagsTerraformCase, dyn } } -// downloadProviders is a test helper that creates a temporary file and writes a -// terraform CLI config file with a provider_installation stanza for coder/coder -// using dev_overrides. It also fetches the latest provider release from GitHub -// and extracts the binary to the temporary dir. It is the responsibility of the -// caller to set TF_CLI_CONFIG_FILE. +// downloadProviders is a test helper that caches Terraform providers and returns +// the path to a Terraform CLI config file that uses the cached providers. +// This uses the shared testutil caching infrastructure to avoid re-downloading +// providers on every test run. It is the responsibility of the caller to set +// TF_CLI_CONFIG_FILE. func downloadProviders(t *testing.T, providersTf string) string { t.Helper() - // We firstly write a Terraform CLI config file to a temporary directory: - var ( - tempDir = t.TempDir() - cacheDir = filepath.Join(tempDir, ".cache") - providersTfPath = filepath.Join(tempDir, "providers.tf") - cliConfigPath = filepath.Join(tempDir, "local.tfrc") - ) - // Write files to disk - require.NoError(t, os.MkdirAll(cacheDir, os.ModePerm|os.ModeDir)) - require.NoError(t, os.WriteFile(providersTfPath, []byte(providersTf), os.ModePerm)) // nolint:gosec - cliConfigTemplate := ` - provider_installation { - filesystem_mirror { - path = %q - include = ["*/*/*"] - } - direct { - exclude = ["*/*/*"] - } - }` - err := os.WriteFile(cliConfigPath, []byte(fmt.Sprintf(cliConfigTemplate, cacheDir)), os.ModePerm) // nolint:gosec - require.NoError(t, err, "failed to write %s", cliConfigPath) - - ctx := testutil.Context(t, testutil.WaitLong) - - // Run terraform providers mirror to mirror required providers to cacheDir - cmd := exec.CommandContext(ctx, "terraform", "providers", "mirror", cacheDir) - cmd.Env = os.Environ() // without this terraform may complain about path - cmd.Env = append(cmd.Env, "TF_CLI_CONFIG_FILE="+cliConfigPath) - cmd.Dir = tempDir - out, err := cmd.CombinedOutput() - if !assert.NoError(t, err) { - t.Log("failed to download providers:") - t.Log(string(out)) - t.FailNow() - } + cacheRootDir := filepath.Join(testutil.PersistentCacheDir(t), "terraform_workspace_tags_test") + templateFiles := map[string]string{"providers.tf": providersTf} + testName := "TestWorkspaceTagsTerraform" + cliConfigPath := testutil.CacheTFProviders(t, cacheRootDir, testName, templateFiles) t.Logf("Set TF_CLI_CONFIG_FILE=%s", cliConfigPath) return cliConfigPath } diff --git a/provisioner/terraform/provision_test.go b/provisioner/terraform/provision_test.go index 450dd04b061a6..9a8a49c29b9ab 100644 --- a/provisioner/terraform/provision_test.go +++ b/provisioner/terraform/provision_test.go @@ -3,17 +3,13 @@ package terraform_test import ( - "bytes" "context" - "crypto/sha256" - "encoding/hex" "encoding/json" "errors" "fmt" "net" "net/http" "os" - "os/exec" "path/filepath" "sort" "strings" @@ -94,168 +90,6 @@ func configure(ctx context.Context, t *testing.T, client proto.DRPCProvisionerCl return sess } -func hashTemplateFilesAndTestName(t *testing.T, testName string, templateFiles map[string]string) string { - t.Helper() - - sortedFileNames := make([]string, 0, len(templateFiles)) - for fileName := range templateFiles { - sortedFileNames = append(sortedFileNames, fileName) - } - sort.Strings(sortedFileNames) - - // Inserting a delimiter between the file name and the file content - // ensures that a file named `ab` with content `cd` - // will not hash to the same value as a file named `abc` with content `d`. - // This can still happen if the file name or content include the delimiter, - // but hopefully they won't. - delimiter := []byte("🎉 🌱 🌷") - - hasher := sha256.New() - for _, fileName := range sortedFileNames { - file := templateFiles[fileName] - _, err := hasher.Write([]byte(fileName)) - require.NoError(t, err) - _, err = hasher.Write(delimiter) - require.NoError(t, err) - _, err = hasher.Write([]byte(file)) - require.NoError(t, err) - } - _, err := hasher.Write(delimiter) - require.NoError(t, err) - _, err = hasher.Write([]byte(testName)) - require.NoError(t, err) - - return hex.EncodeToString(hasher.Sum(nil)) -} - -const ( - terraformConfigFileName = "terraform.rc" - cacheProvidersDirName = "providers" - cacheTemplateFilesDirName = "files" -) - -// Writes a Terraform CLI config file (`terraform.rc`) in `dir` to enforce using the local provider mirror. -// This blocks network access for providers, forcing Terraform to use only what's cached in `dir`. -// Returns the path to the generated config file. -func writeCliConfig(t *testing.T, dir string) string { - t.Helper() - - cliConfigPath := filepath.Join(dir, terraformConfigFileName) - require.NoError(t, os.MkdirAll(filepath.Dir(cliConfigPath), 0o700)) - - content := fmt.Sprintf(` - provider_installation { - filesystem_mirror { - path = "%s" - include = ["*/*"] - } - direct { - exclude = ["*/*"] - } - } - `, filepath.Join(dir, cacheProvidersDirName)) - require.NoError(t, os.WriteFile(cliConfigPath, []byte(content), 0o600)) - return cliConfigPath -} - -func runCmd(t *testing.T, dir string, args ...string) { - t.Helper() - - stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil) - cmd := exec.Command(args[0], args[1:]...) //#nosec - cmd.Dir = dir - cmd.Stdout = stdout - cmd.Stderr = stderr - if err := cmd.Run(); err != nil { - t.Fatalf("failed to run %s: %s\nstdout: %s\nstderr: %s", strings.Join(args, " "), err, stdout.String(), stderr.String()) - } -} - -// Each test gets a unique cache dir based on its name and template files. -// This ensures that tests can download providers in parallel and that they -// will redownload providers if the template files change. -func getTestCacheDir(t *testing.T, rootDir string, testName string, templateFiles map[string]string) string { - t.Helper() - - hash := hashTemplateFilesAndTestName(t, testName, templateFiles) - dir := filepath.Join(rootDir, hash[:12]) - return dir -} - -// Ensures Terraform providers are downloaded and cached locally in a unique directory for the test. -// Uses `terraform init` then `mirror` to populate the cache if needed. -// Returns the cache directory path. -func downloadProviders(t *testing.T, rootDir string, testName string, templateFiles map[string]string) string { - t.Helper() - - dir := getTestCacheDir(t, rootDir, testName, templateFiles) - if _, err := os.Stat(dir); err == nil { - t.Logf("%s: using cached terraform providers", testName) - return dir - } - filesDir := filepath.Join(dir, cacheTemplateFilesDirName) - defer func() { - // The files dir will contain a copy of terraform providers generated - // by the terraform init command. We don't want to persist them since - // we already have a registry mirror in the providers dir. - if err := os.RemoveAll(filesDir); err != nil { - t.Logf("failed to remove files dir %s: %s", filesDir, err) - } - if !t.Failed() { - return - } - // If `downloadProviders` function failed, clean up the cache dir. - // We don't want to leave it around because it may be incomplete or corrupted. - if err := os.RemoveAll(dir); err != nil { - t.Logf("failed to remove dir %s: %s", dir, err) - } - }() - - require.NoError(t, os.MkdirAll(filesDir, 0o700)) - - for fileName, file := range templateFiles { - filePath := filepath.Join(filesDir, fileName) - require.NoError(t, os.MkdirAll(filepath.Dir(filePath), 0o700)) - require.NoError(t, os.WriteFile(filePath, []byte(file), 0o600)) - } - - providersDir := filepath.Join(dir, cacheProvidersDirName) - require.NoError(t, os.MkdirAll(providersDir, 0o700)) - - // We need to run init because if a test uses modules in its template, - // the mirror command will fail without it. - runCmd(t, filesDir, "terraform", "init") - // Now, mirror the providers into `providersDir`. We use this explicit mirror - // instead of relying only on the standard Terraform plugin cache. - // - // Why? Because this mirror, when used with the CLI config from `writeCliConfig`, - // prevents Terraform from hitting the network registry during `plan`. This cuts - // down on network calls, making CI tests less flaky. - // - // In contrast, the standard cache *still* contacts the registry for metadata - // during `init`, even if the plugins are already cached locally - see link below. - // - // Ref: https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache - // > When a plugin cache directory is enabled, the terraform init command will - // > still use the configured or implied installation methods to obtain metadata - // > about which plugins are available - runCmd(t, filesDir, "terraform", "providers", "mirror", providersDir) - - return dir -} - -// Caches providers locally and generates a Terraform CLI config to use *only* that cache. -// This setup prevents network access for providers during `terraform init`, improving reliability -// in subsequent test runs. -// Returns the path to the generated CLI config file. -func cacheProviders(t *testing.T, rootDir string, testName string, templateFiles map[string]string) string { - t.Helper() - - providersParentDir := downloadProviders(t, rootDir, testName, templateFiles) - cliConfigPath := writeCliConfig(t, providersParentDir) - return cliConfigPath -} - func readProvisionLog(t *testing.T, response proto.DRPCProvisioner_SessionClient) string { var logBuf strings.Builder for { @@ -1177,7 +1011,7 @@ func TestProvision(t *testing.T) { cacheRootDir := filepath.Join(testutil.PersistentCacheDir(t), "terraform_provision_test") expectedCacheDirs := make(map[string]bool) for _, testCase := range testCases { - cacheDir := getTestCacheDir(t, cacheRootDir, testCase.Name, testCase.Files) + cacheDir := testutil.GetTestTFCacheDir(t, cacheRootDir, testCase.Name, testCase.Files) expectedCacheDirs[cacheDir] = true } currentCacheDirs, err := filepath.Glob(filepath.Join(cacheRootDir, "*")) @@ -1199,7 +1033,7 @@ func TestProvision(t *testing.T) { cliConfigPath := "" if !testCase.SkipCacheProviders { - cliConfigPath = cacheProviders( + cliConfigPath = testutil.CacheTFProviders( t, cacheRootDir, testCase.Name, diff --git a/testutil/terraform_cache.go b/testutil/terraform_cache.go new file mode 100644 index 0000000000000..f1e21e23c0ef6 --- /dev/null +++ b/testutil/terraform_cache.go @@ -0,0 +1,185 @@ +//go:build linux || darwin + +package testutil + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + // terraformConfigFileName is the name of the Terraform CLI config file. + terraformConfigFileName = "terraform.rc" + // cacheProvidersDirName is the subdirectory name for the provider mirror. + cacheProvidersDirName = "providers" + // cacheTemplateFilesDirName is the subdirectory name for template files. + cacheTemplateFilesDirName = "files" +) + +// hashTemplateFilesAndTestName generates a unique hash based on test name and template files. +func hashTemplateFilesAndTestName(t *testing.T, testName string, templateFiles map[string]string) string { + t.Helper() + + sortedFileNames := make([]string, 0, len(templateFiles)) + for fileName := range templateFiles { + sortedFileNames = append(sortedFileNames, fileName) + } + sort.Strings(sortedFileNames) + + // Inserting a delimiter between the file name and the file content + // ensures that a file named `ab` with content `cd` + // will not hash to the same value as a file named `abc` with content `d`. + // This can still happen if the file name or content include the delimiter, + // but hopefully they won't. + delimiter := []byte("🎉 🌱 🌷") + + hasher := sha256.New() + for _, fileName := range sortedFileNames { + file := templateFiles[fileName] + _, err := hasher.Write([]byte(fileName)) + require.NoError(t, err) + _, err = hasher.Write(delimiter) + require.NoError(t, err) + _, err = hasher.Write([]byte(file)) + require.NoError(t, err) + } + _, err := hasher.Write(delimiter) + require.NoError(t, err) + _, err = hasher.Write([]byte(testName)) + require.NoError(t, err) + + return hex.EncodeToString(hasher.Sum(nil)) +} + +// WriteTFCliConfig writes a Terraform CLI config file (`terraform.rc`) in `dir` to enforce using the local provider mirror. +// This blocks network access for providers, forcing Terraform to use only what's cached in `dir`. +// Returns the path to the generated config file. +func WriteTFCliConfig(t *testing.T, dir string) string { + t.Helper() + + cliConfigPath := filepath.Join(dir, terraformConfigFileName) + require.NoError(t, os.MkdirAll(filepath.Dir(cliConfigPath), 0o700)) + + content := fmt.Sprintf(` + provider_installation { + filesystem_mirror { + path = "%s" + include = ["*/*"] + } + direct { + exclude = ["*/*"] + } + } + `, filepath.Join(dir, cacheProvidersDirName)) + require.NoError(t, os.WriteFile(cliConfigPath, []byte(content), 0o600)) + return cliConfigPath +} + +func runCmd(t *testing.T, dir string, args ...string) { + t.Helper() + + stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil) + cmd := exec.Command(args[0], args[1:]...) //#nosec + cmd.Dir = dir + cmd.Stdout = stdout + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + t.Fatalf("failed to run %s: %s\nstdout: %s\nstderr: %s", strings.Join(args, " "), err, stdout.String(), stderr.String()) + } +} + +// GetTestTFCacheDir returns a unique cache directory path based on the test name and template files. +// Each test gets a unique cache dir based on its name and template files. +// This ensures that tests can download providers in parallel and that they +// will redownload providers if the template files change. +func GetTestTFCacheDir(t *testing.T, rootDir string, testName string, templateFiles map[string]string) string { + t.Helper() + + hash := hashTemplateFilesAndTestName(t, testName, templateFiles) + dir := filepath.Join(rootDir, hash[:12]) + return dir +} + +// DownloadTFProviders ensures Terraform providers are downloaded and cached locally in a unique directory for the test. +// Uses `terraform init` then `mirror` to populate the cache if needed. +// Returns the cache directory path. +func DownloadTFProviders(t *testing.T, rootDir string, testName string, templateFiles map[string]string) string { + t.Helper() + + dir := GetTestTFCacheDir(t, rootDir, testName, templateFiles) + if _, err := os.Stat(dir); err == nil { + t.Logf("%s: using cached terraform providers", testName) + return dir + } + filesDir := filepath.Join(dir, cacheTemplateFilesDirName) + defer func() { + // The files dir will contain a copy of terraform providers generated + // by the terraform init command. We don't want to persist them since + // we already have a registry mirror in the providers dir. + if err := os.RemoveAll(filesDir); err != nil { + t.Logf("failed to remove files dir %s: %s", filesDir, err) + } + if !t.Failed() { + return + } + // If `DownloadTFProviders` function failed, clean up the cache dir. + // We don't want to leave it around because it may be incomplete or corrupted. + if err := os.RemoveAll(dir); err != nil { + t.Logf("failed to remove dir %s: %s", dir, err) + } + }() + + require.NoError(t, os.MkdirAll(filesDir, 0o700)) + + for fileName, file := range templateFiles { + filePath := filepath.Join(filesDir, fileName) + require.NoError(t, os.MkdirAll(filepath.Dir(filePath), 0o700)) + require.NoError(t, os.WriteFile(filePath, []byte(file), 0o600)) + } + + providersDir := filepath.Join(dir, cacheProvidersDirName) + require.NoError(t, os.MkdirAll(providersDir, 0o700)) + + // We need to run init because if a test uses modules in its template, + // the mirror command will fail without it. + runCmd(t, filesDir, "terraform", "init") + // Now, mirror the providers into `providersDir`. We use this explicit mirror + // instead of relying only on the standard Terraform plugin cache. + // + // Why? Because this mirror, when used with the CLI config from `WriteCliConfig`, + // prevents Terraform from hitting the network registry during `plan`. This cuts + // down on network calls, making CI tests less flaky. + // + // In contrast, the standard cache *still* contacts the registry for metadata + // during `init`, even if the plugins are already cached locally - see link below. + // + // Ref: https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache + // > When a plugin cache directory is enabled, the terraform init command will + // > still use the configured or implied installation methods to obtain metadata + // > about which plugins are available + runCmd(t, filesDir, "terraform", "providers", "mirror", providersDir) + + return dir +} + +// CacheTFProviders caches providers locally and generates a Terraform CLI config to use *only* that cache. +// This setup prevents network access for providers during `terraform init`, improving reliability +// in subsequent test runs. +// Returns the path to the generated CLI config file. +func CacheTFProviders(t *testing.T, rootDir string, testName string, templateFiles map[string]string) string { + t.Helper() + + providersParentDir := DownloadTFProviders(t, rootDir, testName, templateFiles) + cliConfigPath := WriteTFCliConfig(t, providersParentDir) + return cliConfigPath +} From 04f809f2d0b4e30e646091e82287c47aa4d4168b Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 12 Nov 2025 11:23:01 +0200 Subject: [PATCH 122/255] chore!: allow coder MCP tools to not be injected (#20713) Currently, when AI Bridge is enabled AND the `oauth2` and `mcp-server-http` experiments are enabled we inject Coder's MCP tools into all intercepted AI Bridge requests. This PR introduces a config to control this behaviour. **NOTE:** this is a backwards-incompatible change; previously these tools would be injected automatically, now this setting will need to be explicitly enabled. --------- Signed-off-by: Danny Kopping --- cli/testdata/coder_server_--help.golden | 5 ++++ cli/testdata/server-config.yaml.golden | 4 +++ coderd/apidoc/docs.go | 3 ++ coderd/apidoc/swagger.json | 3 ++ codersdk/deployment.go | 19 +++++++++--- docs/reference/api/general.md | 1 + docs/reference/api/schemas.md | 17 +++++++---- docs/reference/cli/server.md | 11 +++++++ enterprise/aibridgedserver/aibridgedserver.go | 24 +++++++++------ .../aibridgedserver/aibridgedserver_test.go | 30 +++++++++++++------ .../cli/testdata/coder_server_--help.golden | 5 ++++ enterprise/coderd/aibridged.go | 2 +- site/src/api/typesGenerated.ts | 1 + 13 files changed, 96 insertions(+), 29 deletions(-) diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 7e7a7ece0d958..49ce14b2f572f 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -81,6 +81,11 @@ OPTIONS: check is performed once per day. AIBRIDGE OPTIONS: + --aibridge-inject-coder-mcp-tools bool, $CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS (default: false) + Whether to inject Coder's MCP tools into intercepted AI Bridge + requests (requires the "oauth2" and "mcp-server-http" experiments to + be enabled). + --aibridge-anthropic-base-url string, $CODER_AIBRIDGE_ANTHROPIC_BASE_URL (default: https://api.anthropic.com/) The base URL of the Anthropic API. diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 225c240d9e761..33f5c56c43840 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -747,3 +747,7 @@ aibridge: # https://docs.claude.com/en/docs/claude-code/settings#environment-variables. # (default: global.anthropic.claude-haiku-4-5-20251001-v1:0, type: string) bedrock_small_fast_model: global.anthropic.claude-haiku-4-5-20251001-v1:0 + # Whether to inject Coder's MCP tools into intercepted AI Bridge requests + # (requires the "oauth2" and "mcp-server-http" experiments to be enabled). + # (default: false, type: bool) + inject_coder_mcp_tools: false diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index ff4c39d7c985c..9e6186755721e 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -11700,6 +11700,9 @@ const docTemplate = `{ "enabled": { "type": "boolean" }, + "inject_coder_mcp_tools": { + "type": "boolean" + }, "openai": { "$ref": "#/definitions/codersdk.AIBridgeOpenAIConfig" } diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index a4c639d3feb83..3d6e076ccc17e 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10396,6 +10396,9 @@ "enabled": { "type": "boolean" }, + "inject_coder_mcp_tools": { + "type": "boolean" + }, "openai": { "$ref": "#/definitions/codersdk.AIBridgeOpenAIConfig" } diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 9425a3740f089..4d79058b6835e 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -3339,6 +3339,16 @@ Write out the current server config as YAML to stdout.`, Group: &deploymentGroupAIBridge, YAML: "bedrock_small_fast_model", }, + { + Name: "AI Bridge Inject Coder MCP tools", + Description: "Whether to inject Coder's MCP tools into intercepted AI Bridge requests (requires the \"oauth2\" and \"mcp-server-http\" experiments to be enabled).", + Flag: "aibridge-inject-coder-mcp-tools", + Env: "CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS", + Value: &c.AI.BridgeConfig.InjectCoderMCPTools, + Default: "false", + Group: &deploymentGroupAIBridge, + YAML: "inject_coder_mcp_tools", + }, { Name: "Enable Authorization Recordings", Description: "All api requests will have a header including all authorization calls made during the request. " + @@ -3358,10 +3368,11 @@ Write out the current server config as YAML to stdout.`, } type AIBridgeConfig struct { - Enabled serpent.Bool `json:"enabled" typescript:",notnull"` - OpenAI AIBridgeOpenAIConfig `json:"openai" typescript:",notnull"` - Anthropic AIBridgeAnthropicConfig `json:"anthropic" typescript:",notnull"` - Bedrock AIBridgeBedrockConfig `json:"bedrock" typescript:",notnull"` + Enabled serpent.Bool `json:"enabled" typescript:",notnull"` + OpenAI AIBridgeOpenAIConfig `json:"openai" typescript:",notnull"` + Anthropic AIBridgeAnthropicConfig `json:"anthropic" typescript:",notnull"` + Bedrock AIBridgeBedrockConfig `json:"bedrock" typescript:",notnull"` + InjectCoderMCPTools serpent.Bool `json:"inject_coder_mcp_tools" typescript:",notnull"` } type AIBridgeOpenAIConfig struct { diff --git a/docs/reference/api/general.md b/docs/reference/api/general.md index 5718979ae86c2..a0a24e69772e7 100644 --- a/docs/reference/api/general.md +++ b/docs/reference/api/general.md @@ -175,6 +175,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "small_fast_model": "string" }, "enabled": true, + "inject_coder_mcp_tools": true, "openai": { "base_url": "string", "key": "string" diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index e0b8a7e6c8405..bdab37f2977ee 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -389,6 +389,7 @@ "small_fast_model": "string" }, "enabled": true, + "inject_coder_mcp_tools": true, "openai": { "base_url": "string", "key": "string" @@ -398,12 +399,13 @@ ### Properties -| Name | Type | Required | Restrictions | Description | -|-------------|----------------------------------------------------------------------|----------|--------------|-------------| -| `anthropic` | [codersdk.AIBridgeAnthropicConfig](#codersdkaibridgeanthropicconfig) | false | | | -| `bedrock` | [codersdk.AIBridgeBedrockConfig](#codersdkaibridgebedrockconfig) | false | | | -| `enabled` | boolean | false | | | -| `openai` | [codersdk.AIBridgeOpenAIConfig](#codersdkaibridgeopenaiconfig) | false | | | +| Name | Type | Required | Restrictions | Description | +|--------------------------|----------------------------------------------------------------------|----------|--------------|-------------| +| `anthropic` | [codersdk.AIBridgeAnthropicConfig](#codersdkaibridgeanthropicconfig) | false | | | +| `bedrock` | [codersdk.AIBridgeBedrockConfig](#codersdkaibridgebedrockconfig) | false | | | +| `enabled` | boolean | false | | | +| `inject_coder_mcp_tools` | boolean | false | | | +| `openai` | [codersdk.AIBridgeOpenAIConfig](#codersdkaibridgeopenaiconfig) | false | | | ## codersdk.AIBridgeInterception @@ -695,6 +697,7 @@ "small_fast_model": "string" }, "enabled": true, + "inject_coder_mcp_tools": true, "openai": { "base_url": "string", "key": "string" @@ -2851,6 +2854,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "small_fast_model": "string" }, "enabled": true, + "inject_coder_mcp_tools": true, "openai": { "base_url": "string", "key": "string" @@ -3365,6 +3369,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "small_fast_model": "string" }, "enabled": true, + "inject_coder_mcp_tools": true, "openai": { "base_url": "string", "key": "string" diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md index e689f7fa28336..bcebe05e7e070 100644 --- a/docs/reference/cli/server.md +++ b/docs/reference/cli/server.md @@ -1752,3 +1752,14 @@ The model to use when making requests to the AWS Bedrock API. | Default | global.anthropic.claude-haiku-4-5-20251001-v1:0 | The small fast model to use when making requests to the AWS Bedrock API. Claude Code uses Haiku-class models to perform background tasks. See https://docs.claude.com/en/docs/claude-code/settings#environment-variables. + +### --aibridge-inject-coder-mcp-tools + +| | | +|-------------|-----------------------------------------------------| +| Type | bool | +| Environment | $CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS | +| YAML | aibridge.inject_coder_mcp_tools | +| Default | false | + +Whether to inject Coder's MCP tools into intercepted AI Bridge requests (requires the "oauth2" and "mcp-server-http" experiments to be enabled). diff --git a/enterprise/aibridgedserver/aibridgedserver.go b/enterprise/aibridgedserver/aibridgedserver.go index 78939f39fb3f5..156f3aa9d05da 100644 --- a/enterprise/aibridgedserver/aibridgedserver.go +++ b/enterprise/aibridgedserver/aibridgedserver.go @@ -77,7 +77,9 @@ type Server struct { coderMCPConfig *proto.MCPServerConfig // may be nil if not available } -func NewServer(lifecycleCtx context.Context, store store, logger slog.Logger, accessURL string, externalAuthConfigs []*externalauth.Config, experiments codersdk.Experiments) (*Server, error) { +func NewServer(lifecycleCtx context.Context, store store, logger slog.Logger, accessURL string, + bridgeCfg codersdk.AIBridgeConfig, externalAuthConfigs []*externalauth.Config, experiments codersdk.Experiments, +) (*Server, error) { eac := make(map[string]*externalauth.Config, len(externalAuthConfigs)) for _, cfg := range externalAuthConfigs { @@ -88,18 +90,22 @@ func NewServer(lifecycleCtx context.Context, store store, logger slog.Logger, ac eac[cfg.ID] = cfg } - coderMCPConfig, err := getCoderMCPServerConfig(experiments, accessURL) - if err != nil { - logger.Warn(lifecycleCtx, "failed to retrieve coder MCP server config, Coder MCP will not be available", slog.Error(err)) - } - - return &Server{ + srv := &Server{ lifecycleCtx: lifecycleCtx, store: store, logger: logger.Named("aibridgedserver"), externalAuthConfigs: eac, - coderMCPConfig: coderMCPConfig, - }, nil + } + + if bridgeCfg.InjectCoderMCPTools { + coderMCPConfig, err := getCoderMCPServerConfig(experiments, accessURL) + if err != nil { + logger.Warn(lifecycleCtx, "failed to retrieve coder MCP server config, Coder MCP will not be available", slog.Error(err)) + } + srv.coderMCPConfig = coderMCPConfig + } + + return srv, nil } func (s *Server) RecordInterception(ctx context.Context, in *proto.RecordInterceptionRequest) (*proto.RecordInterceptionResponse, error) { diff --git a/enterprise/aibridgedserver/aibridgedserver_test.go b/enterprise/aibridgedserver/aibridgedserver_test.go index ff8c29d4d29b5..b871bfb3f8e54 100644 --- a/enterprise/aibridgedserver/aibridgedserver_test.go +++ b/enterprise/aibridgedserver/aibridgedserver_test.go @@ -32,6 +32,7 @@ import ( "github.com/coder/coder/v2/enterprise/aibridged/proto" "github.com/coder/coder/v2/enterprise/aibridgedserver" "github.com/coder/coder/v2/testutil" + "github.com/coder/serpent" ) var requiredExperiments = []codersdk.Experiment{ @@ -169,7 +170,7 @@ func TestAuthorization(t *testing.T) { tc.mocksFn(db, apiKey, user) } - srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", nil, requiredExperiments) + srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", codersdk.AIBridgeConfig{}, nil, requiredExperiments) require.NoError(t, err) require.NotNil(t, srv) @@ -203,11 +204,12 @@ func TestGetMCPServerConfigs(t *testing.T) { } cases := []struct { - name string - experiments codersdk.Experiments - externalAuthConfigs []*externalauth.Config - expectCoderMCP bool - expectedExternalMCP bool + name string + disableCoderMCPInjection bool + experiments codersdk.Experiments + externalAuthConfigs []*externalauth.Config + expectCoderMCP bool + expectedExternalMCP bool }{ { name: "experiments not enabled", @@ -238,6 +240,14 @@ func TestGetMCPServerConfigs(t *testing.T) { expectCoderMCP: true, expectedExternalMCP: true, }, + { + name: "both internal & external MCP, but coder MCP tools not injected", + disableCoderMCPInjection: true, + experiments: requiredExperiments, + externalAuthConfigs: externalAuthCfgs, + expectCoderMCP: false, + expectedExternalMCP: true, + }, } for _, tc := range cases { @@ -249,7 +259,9 @@ func TestGetMCPServerConfigs(t *testing.T) { logger := testutil.Logger(t) accessURL := "https://my-cool-deployment.com" - srv, err := aibridgedserver.NewServer(t.Context(), db, logger, accessURL, tc.externalAuthConfigs, tc.experiments) + srv, err := aibridgedserver.NewServer(t.Context(), db, logger, accessURL, codersdk.AIBridgeConfig{ + InjectCoderMCPTools: serpent.Bool(!tc.disableCoderMCPInjection), + }, tc.externalAuthConfigs, tc.experiments) require.NoError(t, err) require.NotNil(t, srv) @@ -287,7 +299,7 @@ func TestGetMCPServerAccessTokensBatch(t *testing.T) { logger := testutil.Logger(t) // Given: 2 external auth configured with MCP and 1 without. - srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", []*externalauth.Config{ + srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", codersdk.AIBridgeConfig{}, []*externalauth.Config{ { ID: "1", MCPURL: "1.com/mcp", @@ -794,7 +806,7 @@ func testRecordMethod[Req any, Resp any]( } ctx := testutil.Context(t, testutil.WaitLong) - srv, err := aibridgedserver.NewServer(ctx, db, logger, "/", nil, requiredExperiments) + srv, err := aibridgedserver.NewServer(ctx, db, logger, "/", codersdk.AIBridgeConfig{}, nil, requiredExperiments) require.NoError(t, err) resp, err := callMethod(srv, ctx, tc.request) diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden index 492306c55882d..d272200609254 100644 --- a/enterprise/cli/testdata/coder_server_--help.golden +++ b/enterprise/cli/testdata/coder_server_--help.golden @@ -82,6 +82,11 @@ OPTIONS: check is performed once per day. AIBRIDGE OPTIONS: + --aibridge-inject-coder-mcp-tools bool, $CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS (default: false) + Whether to inject Coder's MCP tools into intercepted AI Bridge + requests (requires the "oauth2" and "mcp-server-http" experiments to + be enabled). + --aibridge-anthropic-base-url string, $CODER_AIBRIDGE_ANTHROPIC_BASE_URL (default: https://api.anthropic.com/) The base URL of the Anthropic API. diff --git a/enterprise/coderd/aibridged.go b/enterprise/coderd/aibridged.go index 285575df33862..2ff2de902bce1 100644 --- a/enterprise/coderd/aibridged.go +++ b/enterprise/coderd/aibridged.go @@ -49,7 +49,7 @@ func (api *API) CreateInMemoryAIBridgeServer(dialCtx context.Context) (client ai mux := drpcmux.New() srv, err := aibridgedserver.NewServer(api.ctx, api.Database, api.Logger.Named("aibridgedserver"), - api.AccessURL.String(), api.ExternalAuthConfigs, api.AGPL.Experiments) + api.AccessURL.String(), api.DeploymentValues.AI.BridgeConfig, api.ExternalAuthConfigs, api.AGPL.Experiments) if err != nil { return nil, err } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 42374c80afd5e..7b4ee1820c347 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -31,6 +31,7 @@ export interface AIBridgeConfig { readonly openai: AIBridgeOpenAIConfig; readonly anthropic: AIBridgeAnthropicConfig; readonly bedrock: AIBridgeBedrockConfig; + readonly inject_coder_mcp_tools: boolean; } // From codersdk/aibridge.go From e61b0fcf42ff8efbbede5edb51abbb17e52eb8fd Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 12 Nov 2025 12:27:06 +0200 Subject: [PATCH 123/255] chore(codersdk): deprecate HasAITask on WorkspaceBuild (#20732) Closes coder/internal#973 --- coderd/apidoc/docs.go | 1 + coderd/apidoc/swagger.json | 1 + codersdk/workspacebuilds.go | 5 ++-- docs/reference/api/builds.md | 2 +- docs/reference/api/schemas.md | 54 +++++++++++++++++----------------- site/src/api/typesGenerated.ts | 3 ++ 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 9e6186755721e..a67b2cb7c70ad 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -20550,6 +20550,7 @@ const docTemplate = `{ "format": "date-time" }, "has_ai_task": { + "description": "Deprecated: This field has been deprecated in favor of Task WorkspaceID.", "type": "boolean" }, "has_external_agent": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 3d6e076ccc17e..4e1bd64ebe125 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -18884,6 +18884,7 @@ "format": "date-time" }, "has_ai_task": { + "description": "Deprecated: This field has been deprecated in favor of Task WorkspaceID.", "type": "boolean" }, "has_external_agent": { diff --git a/codersdk/workspacebuilds.go b/codersdk/workspacebuilds.go index ff9bf54bbe158..a91148ab2ad9e 100644 --- a/codersdk/workspacebuilds.go +++ b/codersdk/workspacebuilds.go @@ -88,8 +88,9 @@ type WorkspaceBuild struct { DailyCost int32 `json:"daily_cost"` MatchedProvisioners *MatchedProvisioners `json:"matched_provisioners,omitempty"` TemplateVersionPresetID *uuid.UUID `json:"template_version_preset_id" format:"uuid"` - HasAITask *bool `json:"has_ai_task,omitempty"` - HasExternalAgent *bool `json:"has_external_agent,omitempty"` + // Deprecated: This field has been deprecated in favor of Task WorkspaceID. + HasAITask *bool `json:"has_ai_task,omitempty"` + HasExternalAgent *bool `json:"has_external_agent,omitempty"` } // WorkspaceResource describes resources used to create a workspace, for instance: diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md index 5476e84f0848f..dd7323886e179 100644 --- a/docs/reference/api/builds.md +++ b/docs/reference/api/builds.md @@ -1536,7 +1536,7 @@ Status Code **200** | `» created_at` | string(date-time) | false | | | | `» daily_cost` | integer | false | | | | `» deadline` | string(date-time) | false | | | -| `» has_ai_task` | boolean | false | | | +| `» has_ai_task` | boolean | false | | Deprecated: This field has been deprecated in favor of Task WorkspaceID. | | `» has_external_agent` | boolean | false | | | | `» id` | string(uuid) | false | | | | `» initiator_id` | string(uuid) | false | | | diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index bdab37f2977ee..f5ca96c98b045 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -11361,33 +11361,33 @@ If the schedule is empty, the user will be updated to use the default schedule.| ### Properties -| Name | Type | Required | Restrictions | Description | -|------------------------------|-------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------| -| `build_number` | integer | false | | | -| `created_at` | string | false | | | -| `daily_cost` | integer | false | | | -| `deadline` | string | false | | | -| `has_ai_task` | boolean | false | | | -| `has_external_agent` | boolean | false | | | -| `id` | string | false | | | -| `initiator_id` | string | false | | | -| `initiator_name` | string | false | | | -| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | | -| `matched_provisioners` | [codersdk.MatchedProvisioners](#codersdkmatchedprovisioners) | false | | | -| `max_deadline` | string | false | | | -| `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | | -| `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | | -| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | | -| `template_version_id` | string | false | | | -| `template_version_name` | string | false | | | -| `template_version_preset_id` | string | false | | | -| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | | -| `updated_at` | string | false | | | -| `workspace_id` | string | false | | | -| `workspace_name` | string | false | | | -| `workspace_owner_avatar_url` | string | false | | | -| `workspace_owner_id` | string | false | | | -| `workspace_owner_name` | string | false | | Workspace owner name is the username of the owner of the workspace. | +| Name | Type | Required | Restrictions | Description | +|------------------------------|-------------------------------------------------------------------|----------|--------------|--------------------------------------------------------------------------| +| `build_number` | integer | false | | | +| `created_at` | string | false | | | +| `daily_cost` | integer | false | | | +| `deadline` | string | false | | | +| `has_ai_task` | boolean | false | | Deprecated: This field has been deprecated in favor of Task WorkspaceID. | +| `has_external_agent` | boolean | false | | | +| `id` | string | false | | | +| `initiator_id` | string | false | | | +| `initiator_name` | string | false | | | +| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | | +| `matched_provisioners` | [codersdk.MatchedProvisioners](#codersdkmatchedprovisioners) | false | | | +| `max_deadline` | string | false | | | +| `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | | +| `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | | +| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | | +| `template_version_id` | string | false | | | +| `template_version_name` | string | false | | | +| `template_version_preset_id` | string | false | | | +| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | | +| `updated_at` | string | false | | | +| `workspace_id` | string | false | | | +| `workspace_name` | string | false | | | +| `workspace_owner_avatar_url` | string | false | | | +| `workspace_owner_id` | string | false | | | +| `workspace_owner_name` | string | false | | Workspace owner name is the username of the owner of the workspace. | #### Enumerated Values diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 7b4ee1820c347..76a83e00c5545 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -6394,6 +6394,9 @@ export interface WorkspaceBuild { readonly daily_cost: number; readonly matched_provisioners?: MatchedProvisioners; readonly template_version_preset_id: string | null; + /** + * Deprecated: This field has been deprecated in favor of Task WorkspaceID. + */ readonly has_ai_task?: boolean; readonly has_external_agent?: boolean; } From ca94588bd57fda9655e47d1a73a0501b831cf56f Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Wed, 12 Nov 2025 10:36:39 +0000 Subject: [PATCH 124/255] fix: send prebuild job notification after job build db commit (#20693) ## Problem Fix race condition in prebuilds reconciler. Previously, a job notification event was sent to a Go channel before the provisioning database transaction completed. The notification is consumed by a separate goroutine that publishes to PostgreSQL's LISTEN/NOTIFY, using a separate database connection. This creates a potential race: if a provisioner daemon receives the notification and queries for the job before the provisioning transaction commits, it won't find the job in the database. This manifested as a flaky test failure in `TestReinitializeAgent`, where provisioners would occasionally miss newly created jobs. The test uses a 25-second timeout context, while the acquirer's backup polling mechanism checks for jobs every 30 seconds. This made the race condition visible in tests, though in production the backup polling would eventually pick up the job. The solution presented here guarantees that a job notification is only sent after the provisioning database transaction commits. ## Changes * The `provision()` and `provisionDelete()` functions now return the provisioner job instead of sending notifications internally. * A new `publishProvisionerJob()` helper centralizes the notification logic and is called after each transaction completes. Closes: https://github.com/coder/internal/issues/963 --- enterprise/coderd/prebuilds/reconcile.go | 97 +++++++++++++++++------ enterprise/coderd/workspaceagents_test.go | 2 +- 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/enterprise/coderd/prebuilds/reconcile.go b/enterprise/coderd/prebuilds/reconcile.go index f280436ea98c8..17a56d484c9f6 100644 --- a/enterprise/coderd/prebuilds/reconcile.go +++ b/enterprise/coderd/prebuilds/reconcile.go @@ -697,7 +697,8 @@ func (c *StoreReconciler) createPrebuiltWorkspace(ctx context.Context, prebuiltW return xerrors.Errorf("failed to generate unique prebuild ID: %w", err) } - return c.store.InTx(func(db database.Store) error { + var provisionerJob *database.ProvisionerJob + err = c.store.InTx(func(db database.Store) error { template, err := db.GetTemplateByID(ctx, templateID) if err != nil { return xerrors.Errorf("failed to get template: %w", err) @@ -732,11 +733,20 @@ func (c *StoreReconciler) createPrebuiltWorkspace(ctx context.Context, prebuiltW c.logger.Info(ctx, "attempting to create prebuild", slog.F("name", name), slog.F("workspace_id", prebuiltWorkspaceID.String()), slog.F("preset_id", presetID.String())) - return c.provision(ctx, db, prebuiltWorkspaceID, template, presetID, database.WorkspaceTransitionStart, workspace, DeprovisionModeNormal) + provisionerJob, err = c.provision(ctx, db, prebuiltWorkspaceID, template, presetID, database.WorkspaceTransitionStart, workspace, DeprovisionModeNormal) + return err }, &database.TxOptions{ Isolation: sql.LevelRepeatableRead, ReadOnly: false, }) + if err != nil { + return err + } + + // Publish provisioner job event to notify the acquirer that a new job was posted + c.publishProvisionerJob(ctx, provisionerJob, prebuiltWorkspaceID) + + return nil } // provisionDelete provisions a delete transition for a prebuilt workspace. @@ -748,26 +758,25 @@ func (c *StoreReconciler) createPrebuiltWorkspace(ctx context.Context, prebuiltW // // IMPORTANT: This function must be called within a database transaction. It does not create its own transaction. // The caller is responsible for managing the transaction boundary via db.InTx(). -func (c *StoreReconciler) provisionDelete(ctx context.Context, db database.Store, workspaceID uuid.UUID, templateID uuid.UUID, presetID uuid.UUID, mode DeprovisionMode) error { +func (c *StoreReconciler) provisionDelete(ctx context.Context, db database.Store, workspaceID uuid.UUID, templateID uuid.UUID, presetID uuid.UUID, mode DeprovisionMode) (*database.ProvisionerJob, error) { workspace, err := db.GetWorkspaceByID(ctx, workspaceID) if err != nil { - return xerrors.Errorf("get workspace by ID: %w", err) + return nil, xerrors.Errorf("get workspace by ID: %w", err) } template, err := db.GetTemplateByID(ctx, templateID) if err != nil { - return xerrors.Errorf("failed to get template: %w", err) + return nil, xerrors.Errorf("failed to get template: %w", err) } if workspace.OwnerID != database.PrebuildsSystemUserID { - return xerrors.Errorf("prebuilt workspace is not owned by prebuild user anymore, probably it was claimed") + return nil, xerrors.Errorf("prebuilt workspace is not owned by prebuild user anymore, probably it was claimed") } c.logger.Info(ctx, "attempting to delete prebuild", slog.F("orphan", mode.String()), slog.F("name", workspace.Name), slog.F("workspace_id", workspaceID.String()), slog.F("preset_id", presetID.String())) - return c.provision(ctx, db, workspaceID, template, presetID, - database.WorkspaceTransitionDelete, workspace, mode) + return c.provision(ctx, db, workspaceID, template, presetID, database.WorkspaceTransitionDelete, workspace, mode) } // cancelAndOrphanDeletePendingPrebuilds cancels pending prebuild jobs from inactive template versions @@ -779,7 +788,9 @@ func (c *StoreReconciler) provisionDelete(ctx context.Context, db database.Store // Since these jobs were never processed by a provisioner, no Terraform resources were created, // making it safe to orphan-delete the workspaces (skipping Terraform destroy). func (c *StoreReconciler) cancelAndOrphanDeletePendingPrebuilds(ctx context.Context, templateID uuid.UUID, templateVersionID uuid.UUID, presetID uuid.UUID) error { - return c.store.InTx(func(db database.Store) error { + var canceledProvisionerJob *database.ProvisionerJob + var canceledWorkspaceID uuid.UUID + err := c.store.InTx(func(db database.Store) error { canceledJobs, err := db.UpdatePrebuildProvisionerJobWithCancel( ctx, database.UpdatePrebuildProvisionerJobWithCancelParams{ @@ -808,11 +819,14 @@ func (c *StoreReconciler) cancelAndOrphanDeletePendingPrebuilds(ctx context.Cont var multiErr multierror.Error for _, job := range canceledJobs { - err = c.provisionDelete(ctx, db, job.WorkspaceID, job.TemplateID, presetID, DeprovisionModeOrphan) + provisionerJob, err := c.provisionDelete(ctx, db, job.WorkspaceID, job.TemplateID, presetID, DeprovisionModeOrphan) if err != nil { c.logger.Error(ctx, "failed to orphan delete canceled prebuild", slog.F("workspace_id", job.WorkspaceID.String()), slog.Error(err)) multiErr.Errors = append(multiErr.Errors, err) + } else if canceledProvisionerJob == nil { + canceledProvisionerJob = provisionerJob + canceledWorkspaceID = job.WorkspaceID } } @@ -821,15 +835,38 @@ func (c *StoreReconciler) cancelAndOrphanDeletePendingPrebuilds(ctx context.Cont Isolation: sql.LevelRepeatableRead, ReadOnly: false, }) + if err != nil { + return err + } + + // Job event notifications contain organization, provisioner type, and tags. + // Since all canceled jobs have the same values, we only send one notification + // for the first successfully canceled job, which is sufficient to trigger the + // provisioner chain that processes all remaining jobs. + if canceledProvisionerJob != nil { + c.publishProvisionerJob(ctx, canceledProvisionerJob, canceledWorkspaceID) + } + + return nil } func (c *StoreReconciler) deletePrebuiltWorkspace(ctx context.Context, prebuiltWorkspaceID uuid.UUID, templateID uuid.UUID, presetID uuid.UUID) error { - return c.store.InTx(func(db database.Store) error { - return c.provisionDelete(ctx, db, prebuiltWorkspaceID, templateID, presetID, DeprovisionModeNormal) + var provisionerJob *database.ProvisionerJob + err := c.store.InTx(func(db database.Store) (err error) { + provisionerJob, err = c.provisionDelete(ctx, db, prebuiltWorkspaceID, templateID, presetID, DeprovisionModeNormal) + return err }, &database.TxOptions{ Isolation: sql.LevelRepeatableRead, ReadOnly: false, }) + if err != nil { + return err + } + + // Publish provisioner job event to notify the acquirer that a new job was posted + c.publishProvisionerJob(ctx, provisionerJob, prebuiltWorkspaceID) + + return nil } func (c *StoreReconciler) provision( @@ -841,10 +878,10 @@ func (c *StoreReconciler) provision( transition database.WorkspaceTransition, workspace database.Workspace, mode DeprovisionMode, -) error { +) (*database.ProvisionerJob, error) { tvp, err := db.GetPresetParametersByTemplateVersionID(ctx, template.ActiveVersionID) if err != nil { - return xerrors.Errorf("fetch preset details: %w", err) + return nil, xerrors.Errorf("fetch preset details: %w", err) } var params []codersdk.WorkspaceBuildParameter @@ -893,26 +930,34 @@ func (c *StoreReconciler) provision( audit.WorkspaceBuildBaggage{}, ) if err != nil { - return xerrors.Errorf("provision workspace: %w", err) + return nil, xerrors.Errorf("provision workspace: %w", err) } - if provisionerJob == nil { - return nil - } - - // Publish provisioner job event outside of transaction. - select { - case c.provisionNotifyCh <- *provisionerJob: - default: // channel full, drop the message; provisioner will pick this job up later with its periodic check, though. - c.logger.Warn(ctx, "provisioner job notification queue full, dropping", - slog.F("job_id", provisionerJob.ID), slog.F("prebuild_id", prebuildID.String())) + // This should not happen, builder.Build() should either return a job or an error. + // Returning an error to fail fast if we hit this unexpected case. + return nil, xerrors.Errorf("provision succeeded but returned no job") } c.logger.Info(ctx, "prebuild job scheduled", slog.F("transition", transition), slog.F("prebuild_id", prebuildID.String()), slog.F("preset_id", presetID.String()), slog.F("job_id", provisionerJob.ID)) - return nil + return provisionerJob, nil +} + +// publishProvisionerJob publishes a provisioner job event to notify the acquirer that a new job has been created. +// This must be called after the database transaction that creates the job has committed to ensure +// the job is visible to provisioners when they query the database. +func (c *StoreReconciler) publishProvisionerJob(ctx context.Context, provisionerJob *database.ProvisionerJob, workspaceID uuid.UUID) { + if provisionerJob == nil { + return + } + select { + case c.provisionNotifyCh <- *provisionerJob: + default: // channel full, drop the message; provisioner will pick this job up later with its periodic check + c.logger.Warn(ctx, "provisioner job notification queue full, dropping", + slog.F("job_id", provisionerJob.ID), slog.F("prebuild_id", workspaceID.String())) + } } // ForceMetricsUpdate forces the metrics collector, if defined, to update its state (we cache the metrics state to diff --git a/enterprise/coderd/workspaceagents_test.go b/enterprise/coderd/workspaceagents_test.go index 2e4690bc961a9..a150c0cdc06d5 100644 --- a/enterprise/coderd/workspaceagents_test.go +++ b/enterprise/coderd/workspaceagents_test.go @@ -95,7 +95,7 @@ func TestReinitializeAgent(t *testing.T) { // Ensure that workspace agents can reinitialize against claimed prebuilds in non-default organizations: for _, useDefaultOrg := range []bool{true, false} { - t.Run("", func(t *testing.T) { + t.Run(fmt.Sprintf("useDefaultOrg=%t", useDefaultOrg), func(t *testing.T) { t.Parallel() tempAgentLog := testutil.CreateTemp(t, "", "testReinitializeAgent") From 6dbde523ae1824df8b1f5c29bfad0f53acacae98 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Wed, 12 Nov 2025 14:37:22 +0400 Subject: [PATCH 125/255] fix: omit body field from SDK client request/response logs when not logging bodies (#20729) In SDK request logs, when we don't enable LogBodies, before this change it looks in the logs like we are sending empty bodies. ``` 2025-11-08 01:03:54.710 [debu] sdk request method=POST url=https://coder.example/api/v2/workspaceagents/aws-instance-identity body="" 2025-11-08 01:03:54.765 [debu] sdk response method=POST url=https://coder.example/api/v2/workspaceagents/aws-instance-identity status=400 body="" trace_id="" span_id="" ``` This changes our request and response logging so we omit the `body` field when not logging bodies, rather than show a misleading empty string. --- codersdk/client.go | 21 +++++++++-------- codersdk/client_internal_test.go | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/codersdk/client.go b/codersdk/client.go index 42ad51286f181..72dd7ac4b64f4 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -251,16 +251,17 @@ func (c *Client) RequestWithoutSessionToken(ctx context.Context, method, path st } // Copy the request body so we can log it. - var reqBody []byte + var reqLogFields []any c.mu.RLock() logBodies := c.logBodies c.mu.RUnlock() if r != nil && logBodies { - reqBody, err = io.ReadAll(r) + reqBody, err := io.ReadAll(r) if err != nil { return nil, xerrors.Errorf("read request body: %w", err) } r = bytes.NewReader(reqBody) + reqLogFields = append(reqLogFields, slog.F("body", string(reqBody))) } req, err := http.NewRequestWithContext(ctx, method, serverURL.String(), r) @@ -291,7 +292,7 @@ func (c *Client) RequestWithoutSessionToken(ctx context.Context, method, path st slog.F("url", req.URL.String()), ) tracing.RunWithoutSpan(ctx, func(ctx context.Context) { - c.Logger().Debug(ctx, "sdk request", slog.F("body", string(reqBody))) + c.Logger().Debug(ctx, "sdk request", reqLogFields...) }) resp, err := c.HTTPClient.Do(req) @@ -324,11 +325,11 @@ func (c *Client) RequestWithoutSessionToken(ctx context.Context, method, path st span.SetStatus(httpconv.ClientStatus(resp.StatusCode)) // Copy the response body so we can log it if it's a loggable mime type. - var respBody []byte + var respLogFields []any if resp.Body != nil && logBodies { mimeType := parseMimeType(resp.Header.Get("Content-Type")) if _, ok := loggableMimeTypes[mimeType]; ok { - respBody, err = io.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, xerrors.Errorf("copy response body for logs: %w", err) } @@ -337,16 +338,18 @@ func (c *Client) RequestWithoutSessionToken(ctx context.Context, method, path st return nil, xerrors.Errorf("close response body: %w", err) } resp.Body = io.NopCloser(bytes.NewReader(respBody)) + respLogFields = append(respLogFields, slog.F("body", string(respBody))) } } // See above for why this is not logged to the span. tracing.RunWithoutSpan(ctx, func(ctx context.Context) { c.Logger().Debug(ctx, "sdk response", - slog.F("status", resp.StatusCode), - slog.F("body", string(respBody)), - slog.F("trace_id", resp.Header.Get("X-Trace-Id")), - slog.F("span_id", resp.Header.Get("X-Span-Id")), + append(respLogFields, + slog.F("status", resp.StatusCode), + slog.F("trace_id", resp.Header.Get("X-Trace-Id")), + slog.F("span_id", resp.Header.Get("X-Span-Id")), + )..., ) }) diff --git a/codersdk/client_internal_test.go b/codersdk/client_internal_test.go index cfd8bdbf26086..415e88ac9c9fc 100644 --- a/codersdk/client_internal_test.go +++ b/codersdk/client_internal_test.go @@ -162,6 +162,45 @@ func Test_Client(t *testing.T) { require.Contains(t, logStr, strings.ReplaceAll(resBody, `"`, `\"`)) } +func Test_Client_LogBodiesFalse(t *testing.T) { + t.Parallel() + + const method = http.MethodPost + const path = "/ok" + const reqBody = `{"msg": "request body"}` + const resBody = `{"status": "ok"}` + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", jsonCT) + w.WriteHeader(http.StatusOK) + _, _ = io.WriteString(w, resBody) + })) + + u, err := url.Parse(s.URL) + require.NoError(t, err) + client := New(u) + + logBuf := bytes.NewBuffer(nil) + client.SetLogger(slog.Make(sloghuman.Sink(logBuf)).Leveled(slog.LevelDebug)) + client.SetLogBodies(false) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + resp, err := client.Request(ctx, method, path, []byte(reqBody)) + require.NoError(t, err) + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, resBody, string(body)) + + logStr := logBuf.String() + require.Contains(t, logStr, "sdk request") + require.Contains(t, logStr, "sdk response") + require.NotContains(t, logStr, "body") +} + func Test_readBodyAsError(t *testing.T) { t.Parallel() From f59763968ffc9b03a4a36ac7f9c9ed01f8d095ed Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 12 Nov 2025 11:14:20 +0000 Subject: [PATCH 126/255] ci(.github/workflows/traiage.yaml): adjust TASK_PROMPT (#20733) Adjusts instructions for the trAIage workflow to balance autonomy and correctness. --- .github/workflows/traiage.yaml | 17 +++++++---------- .gitignore | 3 +++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/traiage.yaml b/.github/workflows/traiage.yaml index 044a0e38cdcec..ec08069538161 100644 --- a/.github/workflows/traiage.yaml +++ b/.github/workflows/traiage.yaml @@ -133,18 +133,15 @@ jobs: issue_number="$(gh issue view "${ISSUE_URL}" --json number --jq '.number')" context_key="gh-${issue_number}" - # Fetch issue description using `gh` CLI - #shellcheck disable=SC2016 # The template string should not be subject to shell expansion - issue_description=$(gh issue view "${ISSUE_URL}" \ - --json 'title,body,comments' \ - --template '{{printf "%s\n\n%s\n\nComments:\n" .title .body}}{{range $k, $v := .comments}} - {{index $v.author "login"}}: {{printf "%s\n" $v.body}}{{end}}') - TASK_PROMPT=$(cat < Date: Wed, 12 Nov 2025 13:17:33 +0200 Subject: [PATCH 127/255] fix: upgrade aibridge lib to fix cache issue (#20730) https://github.com/coder/aibridge/pull/49 fixed an issue with tool ordering which was busting the cache and leading to increased costs Signed-off-by: Danny Kopping --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f11d307eb2e07..49bdd7805982c 100644 --- a/go.mod +++ b/go.mod @@ -476,7 +476,7 @@ require ( github.com/anthropics/anthropic-sdk-go v1.17.0 github.com/brianvoe/gofakeit/v7 v7.9.0 github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 - github.com/coder/aibridge v0.1.6 + github.com/coder/aibridge v0.1.7 github.com/coder/aisdk-go v0.0.9 github.com/coder/boundary v1.0.1-0.20250925154134-55a44f2a7945 github.com/coder/preview v1.0.4 diff --git a/go.sum b/go.sum index 4ac6b4fd78fff..455f78d661fa4 100644 --- a/go.sum +++ b/go.sum @@ -917,8 +917,8 @@ github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 h1:tRIViZ5JRmzdOEo5wUWngaGEFBG8OaE1o2GIHN5ujJ8= github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225/go.mod h1:rNLVpYgEVeu1Zk29K64z6Od8RBP9DwqCu9OfCzh8MR4= -github.com/coder/aibridge v0.1.6 h1:Ax9hfAQeg+vLeE9M/IMmARuBuGyCuFdMw8bpy72/fD8= -github.com/coder/aibridge v0.1.6/go.mod h1:7GhrLbzf6uM3sCA7OPaDzvq9QNrCjNuzMy+WgipYwfQ= +github.com/coder/aibridge v0.1.7 h1:GTAM8nHawXMeb/pxAIwvzr76dyVGu9hw9qV6Gvpc7nw= +github.com/coder/aibridge v0.1.7/go.mod h1:7GhrLbzf6uM3sCA7OPaDzvq9QNrCjNuzMy+WgipYwfQ= github.com/coder/aisdk-go v0.0.9 h1:Vzo/k2qwVGLTR10ESDeP2Ecek1SdPfZlEjtTfMveiVo= github.com/coder/aisdk-go v0.0.9/go.mod h1:KF6/Vkono0FJJOtWtveh5j7yfNrSctVTpwgweYWSp5M= github.com/coder/boundary v1.0.1-0.20250925154134-55a44f2a7945 h1:hDUf02kTX8EGR3+5B+v5KdYvORs4YNfDPci0zCs+pC0= From c69eb7c15764ac669bd1e4a33e9199d145c013c0 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 12 Nov 2025 14:02:04 +0200 Subject: [PATCH 128/255] docs: reflect steps required to enable coder MCP tool injection (#20735) Follow-up from #20713 Signed-off-by: Danny Kopping --- docs/ai-coder/ai-bridge/mcp.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ai-coder/ai-bridge/mcp.md b/docs/ai-coder/ai-bridge/mcp.md index 576c4d83b7f31..ef173f8b3ec46 100644 --- a/docs/ai-coder/ai-bridge/mcp.md +++ b/docs/ai-coder/ai-bridge/mcp.md @@ -55,12 +55,12 @@ If a model decides to invoke a tool and it has a `bmcp_` suffix and AI Bridge ha In contrast, tools which are defined by the client (i.e. the [`Bash` tool](https://docs.claude.com/en/docs/claude-code/settings#tools-available-to-claude) defined by _Claude Code_) cannot be invoked by AI Bridge, and the tool call from the model will be relayed to the client, after which it will invoke the tool. -If you have the `oauth2` and `mcp-server-http` experiments enabled, Coder's own [internal MCP tools](../mcp-server.md) will be injected automatically. +If you have [Coder MCP Server](../mcp-server.md) enabled, as well as have [`CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS=true`](../../reference/cli/server#--aibridge-inject-coder-mcp-tools) set, Coder's MCP tools will be injected into intercepted requests. ### Troubleshooting - **Too many tools**: should you receive an error like `Invalid 'tools': array too long. Expected an array with maximum length 128, but got an array with length 132 instead`, you can reduce the number by filtering out tools using the allow/deny patterns documented in the [MCP](#mcp) section. -- **Coder MCP tools not being injected**: in order for Coder MCP tools to be injected, the internal MCP server needs to be active. Follow the instructions in the [MCP Server](../mcp-server.md) page to enable it. +- **Coder MCP tools not being injected**: in order for Coder MCP tools to be injected, the internal MCP server needs to be active. Follow the instructions in the [MCP Server](../mcp-server.md) page to enable it and ensure `CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS` is set to `true`. - **External Auth tools not being injected**: this is generally due to the requesting user not being authenticated against the [External Auth](../../admin/external-auth/index.md) app; when this is the case, no attempt is made to connect to the MCP server. From 7c8deaf0d6bfb4d9fb77127345888f53554517a8 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 12 Nov 2025 09:24:07 -0600 Subject: [PATCH 129/255] chore: refactor terraform paths to a central structure (#20566) Refactors all Terraform file path logic into a centralized tfpath package. This consolidates all path construction into a single, testable Layout type. Instead of passing around `string` for directories, pass around the `Layout` which has the file location methods on it. --- provisioner/echo/serve.go | 4 +- provisioner/terraform/executor.go | 30 ++--- provisioner/terraform/modules.go | 10 +- provisioner/terraform/parse.go | 4 +- provisioner/terraform/provision.go | 12 +- provisioner/terraform/serve.go | 5 +- provisionerd/provisionerd_test.go | 2 +- provisionersdk/cleanup_test.go | 24 ++-- provisionersdk/session.go | 135 ++------------------ provisionersdk/tfpath/tfpath.go | 192 +++++++++++++++++++++++++++++ 10 files changed, 244 insertions(+), 174 deletions(-) create mode 100644 provisionersdk/tfpath/tfpath.go diff --git a/provisioner/echo/serve.go b/provisioner/echo/serve.go index 5069424156009..26d1fcbe3ad06 100644 --- a/provisioner/echo/serve.go +++ b/provisioner/echo/serve.go @@ -122,8 +122,8 @@ func readResponses(sess *provisionersdk.Session, trans string, suffix string) ([ for i := 0; ; i++ { paths := []string{ // Try more specific path first, then fallback to generic. - filepath.Join(sess.WorkDirectory, fmt.Sprintf("%d.%s.%s", i, trans, suffix)), - filepath.Join(sess.WorkDirectory, fmt.Sprintf("%d.%s", i, suffix)), + filepath.Join(sess.Files.WorkDirectory(), fmt.Sprintf("%d.%s.%s", i, trans, suffix)), + filepath.Join(sess.Files.WorkDirectory(), fmt.Sprintf("%d.%s", i, suffix)), } for pathIndex, path := range paths { _, err := os.Stat(path) diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index c7811fe2721dd..345b0e72fb90c 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -10,7 +10,6 @@ import ( "io" "os" "os/exec" - "path/filepath" "runtime" "strings" "sync" @@ -22,6 +21,7 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + "github.com/coder/coder/v2/provisionersdk/tfpath" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/tracing" @@ -38,10 +38,10 @@ type executor struct { server *server mut *sync.Mutex binaryPath string - // cachePath and workdir must not be used by multiple processes at once. + // cachePath and files must not be used by multiple processes at once. cachePath string cliConfigPath string - workdir string + files tfpath.Layout // used to capture execution times at various stages timings *timingAggregator } @@ -90,7 +90,7 @@ func (e *executor) execWriteOutput(ctx, killCtx context.Context, args, env []str // #nosec cmd := exec.CommandContext(killCtx, e.binaryPath, args...) - cmd.Dir = e.workdir + cmd.Dir = e.files.WorkDirectory() if env == nil { // We don't want to passthrough host env when unset. env = []string{} @@ -131,7 +131,7 @@ func (e *executor) execParseJSON(ctx, killCtx context.Context, args, env []strin // #nosec cmd := exec.CommandContext(killCtx, e.binaryPath, args...) - cmd.Dir = e.workdir + cmd.Dir = e.files.WorkDirectory() cmd.Env = env out := &bytes.Buffer{} stdErr := &bytes.Buffer{} @@ -225,7 +225,7 @@ func (e *executor) init(ctx, killCtx context.Context, logr logSink) error { defer e.mut.Unlock() // Record lock file checksum before init - lockFilePath := filepath.Join(e.workdir, ".terraform.lock.hcl") + lockFilePath := e.files.TerraformLockFile() preInitChecksum := checksumFileCRC32(ctx, e.logger, lockFilePath) outWriter, doneOut := e.provisionLogWriter(logr) @@ -289,14 +289,6 @@ func checksumFileCRC32(ctx context.Context, logger slog.Logger, path string) uin return crc32.ChecksumIEEE(content) } -func getPlanFilePath(workdir string) string { - return filepath.Join(workdir, "terraform.tfplan") -} - -func getStateFilePath(workdir string) string { - return filepath.Join(workdir, "terraform.tfstate") -} - // revive:disable-next-line:flag-parameter func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr logSink, req *proto.PlanRequest) (*proto.PlanComplete, error) { ctx, span := e.server.startTrace(ctx, tracing.FuncName()) @@ -307,7 +299,7 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l metadata := req.Metadata - planfilePath := getPlanFilePath(e.workdir) + planfilePath := e.files.PlanFilePath() args := []string{ "plan", "-no-color", @@ -359,7 +351,7 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l // a workspace build. This removes some added costs of sending the modules // payload back to coderd if coderd is just going to ignore it. if !req.OmitModuleFiles { - moduleFiles, err = GetModulesArchive(os.DirFS(e.workdir)) + moduleFiles, err = GetModulesArchive(os.DirFS(e.files.WorkDirectory())) if err != nil { // TODO: we probably want to persist this error or make it louder eventually e.logger.Warn(ctx, "failed to archive terraform modules", slog.Error(err)) @@ -551,7 +543,7 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) { var out strings.Builder cmd := exec.CommandContext(killCtx, e.binaryPath, args...) // #nosec cmd.Stdout = &out - cmd.Dir = e.workdir + cmd.Dir = e.files.WorkDirectory() cmd.Env = e.basicEnv() e.server.logger.Debug(ctx, "executing terraform command graph", @@ -588,7 +580,7 @@ func (e *executor) apply( "-auto-approve", "-input=false", "-json", - getPlanFilePath(e.workdir), + e.files.PlanFilePath(), } outWriter, doneOut := e.provisionLogWriter(logr) @@ -608,7 +600,7 @@ func (e *executor) apply( if err != nil { return nil, err } - statefilePath := getStateFilePath(e.workdir) + statefilePath := e.files.StateFilePath() stateContent, err := os.ReadFile(statefilePath) if err != nil { return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err) diff --git a/provisioner/terraform/modules.go b/provisioner/terraform/modules.go index f0b40ea9517e0..38bfd65e84d6c 100644 --- a/provisioner/terraform/modules.go +++ b/provisioner/terraform/modules.go @@ -7,7 +7,6 @@ import ( "io" "io/fs" "os" - "path/filepath" "strings" "time" @@ -15,6 +14,7 @@ import ( "github.com/coder/coder/v2/coderd/util/xio" "github.com/coder/coder/v2/provisionersdk/proto" + "github.com/coder/coder/v2/provisionersdk/tfpath" ) const ( @@ -39,10 +39,6 @@ type modulesFile struct { Modules []*module `json:"Modules"` } -func getModulesFilePath(workdir string) string { - return filepath.Join(workdir, ".terraform", "modules", "modules.json") -} - func parseModulesFile(filePath string) ([]*proto.Module, error) { modules := &modulesFile{} data, err := os.ReadFile(filePath) @@ -62,8 +58,8 @@ func parseModulesFile(filePath string) ([]*proto.Module, error) { // getModules returns the modules from the modules file if it exists. // It returns nil if the file does not exist. // Modules become available after terraform init. -func getModules(workdir string) ([]*proto.Module, error) { - filePath := getModulesFilePath(workdir) +func getModules(files tfpath.Layout) ([]*proto.Module, error) { + filePath := files.ModulesFilePath() if _, err := os.Stat(filePath); os.IsNotExist(err) { return nil, nil } diff --git a/provisioner/terraform/parse.go b/provisioner/terraform/parse.go index d5b59df327f65..2f5a8c7f5c38a 100644 --- a/provisioner/terraform/parse.go +++ b/provisioner/terraform/parse.go @@ -25,9 +25,9 @@ func (s *server) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <- defer span.End() // Load the module and print any parse errors. - parser, diags := tfparse.New(sess.WorkDirectory, tfparse.WithLogger(s.logger.Named("tfparse"))) + parser, diags := tfparse.New(sess.Files.WorkDirectory(), tfparse.WithLogger(s.logger.Named("tfparse"))) if diags.HasErrors() { - return provisionersdk.ParseErrorf("load module: %s", formatDiagnostics(sess.WorkDirectory, diags)) + return provisionersdk.ParseErrorf("load module: %s", formatDiagnostics(sess.Files.WorkDirectory(), diags)) } workspaceTags, _, err := parser.WorkspaceTags(ctx) diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index ec9f96c3ed397..2445a396f6145 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -76,7 +76,7 @@ func (s *server) Plan( defer cancel() defer kill() - e := s.executor(sess.WorkDirectory, database.ProvisionerJobTimingStagePlan) + e := s.executor(sess.Files, database.ProvisionerJobTimingStagePlan) if err := e.checkMinVersion(ctx); err != nil { return provisionersdk.PlanErrorf("%s", err.Error()) } @@ -92,7 +92,7 @@ func (s *server) Plan( return &proto.PlanComplete{} } - statefilePath := getStateFilePath(sess.WorkDirectory) + statefilePath := sess.Files.StateFilePath() if len(sess.Config.State) > 0 { err := os.WriteFile(statefilePath, sess.Config.State, 0o600) if err != nil { @@ -141,7 +141,7 @@ func (s *server) Plan( return provisionersdk.PlanErrorf("initialize terraform: %s", err) } - modules, err := getModules(sess.WorkDirectory) + modules, err := getModules(sess.Files) if err != nil { // We allow getModules to fail, as the result is used only // for telemetry purposes now. @@ -184,7 +184,7 @@ func (s *server) Apply( defer cancel() defer kill() - e := s.executor(sess.WorkDirectory, database.ProvisionerJobTimingStageApply) + e := s.executor(sess.Files, database.ProvisionerJobTimingStageApply) if err := e.checkMinVersion(ctx); err != nil { return provisionersdk.ApplyErrorf("%s", err.Error()) } @@ -201,7 +201,7 @@ func (s *server) Apply( } // Earlier in the session, Plan() will have written the state file and the plan file. - statefilePath := getStateFilePath(sess.WorkDirectory) + statefilePath := sess.Files.StateFilePath() env, err := provisionEnv(sess.Config, request.Metadata, nil, nil, nil) if err != nil { return provisionersdk.ApplyErrorf("provision env: %s", err) @@ -348,7 +348,7 @@ func logTerraformEnvVars(sink logSink) { // shipped in v1.0.4. It will return the stacktraces of the provider, which will hopefully allow us // to figure out why it hasn't exited. func tryGettingCoderProviderStacktrace(sess *provisionersdk.Session) string { - path := filepath.Clean(filepath.Join(sess.WorkDirectory, "../.coder/pprof")) + path := filepath.Clean(filepath.Join(sess.Files.WorkDirectory(), "../.coder/pprof")) sess.Logger.Info(sess.Context(), "attempting to get stack traces", slog.F("path", path)) c := http.Client{ Transport: &http.Transport{ diff --git a/provisioner/terraform/serve.go b/provisioner/terraform/serve.go index 3e671b0c68e56..60951a8da136b 100644 --- a/provisioner/terraform/serve.go +++ b/provisioner/terraform/serve.go @@ -14,6 +14,7 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + "github.com/coder/coder/v2/provisionersdk/tfpath" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/jobreaper" @@ -160,14 +161,14 @@ func (s *server) startTrace(ctx context.Context, name string, opts ...trace.Span ))...) } -func (s *server) executor(workdir string, stage database.ProvisionerJobTimingStage) *executor { +func (s *server) executor(files tfpath.Layout, stage database.ProvisionerJobTimingStage) *executor { return &executor{ server: s, mut: s.execMut, binaryPath: s.binaryPath, cachePath: s.cachePath, cliConfigPath: s.cliConfigPath, - workdir: workdir, + files: files, logger: s.logger.Named("executor"), timings: newTimingAggregator(stage), } diff --git a/provisionerd/provisionerd_test.go b/provisionerd/provisionerd_test.go index 1b4b6720b48e9..f9977d0e8eb1a 100644 --- a/provisionerd/provisionerd_test.go +++ b/provisionerd/provisionerd_test.go @@ -353,7 +353,7 @@ func TestProvisionerd(t *testing.T) { _ *sdkproto.ParseRequest, cancelOrComplete <-chan struct{}, ) *sdkproto.ParseComplete { - data, err := os.ReadFile(filepath.Join(s.WorkDirectory, "test.txt")) + data, err := os.ReadFile(filepath.Join(s.Files.WorkDirectory(), "test.txt")) require.NoError(t, err) require.Equal(t, "content", string(data)) s.ProvisionLog(sdkproto.LogLevel_INFO, "hello") diff --git a/provisionersdk/cleanup_test.go b/provisionersdk/cleanup_test.go index e23c7a9f78f9a..d60ef55a7c6d4 100644 --- a/provisionersdk/cleanup_test.go +++ b/provisionersdk/cleanup_test.go @@ -12,6 +12,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/provisionersdk" + "github.com/coder/coder/v2/provisionersdk/tfpath" "github.com/coder/coder/v2/testutil" ) @@ -40,11 +41,11 @@ func TestStaleSessions(t *testing.T) { fs, logger := prepare() // given - first := provisionersdk.SessionDir(uuid.NewString()) + first := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, first, now.Add(-7*24*time.Hour)) - second := provisionersdk.SessionDir(uuid.NewString()) + second := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, second, now.Add(-8*24*time.Hour)) - third := provisionersdk.SessionDir(uuid.NewString()) + third := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, third, now.Add(-9*24*time.Hour)) // when @@ -65,9 +66,9 @@ func TestStaleSessions(t *testing.T) { fs, logger := prepare() // given - first := provisionersdk.SessionDir(uuid.NewString()) + first := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, first, now.Add(-7*24*time.Hour)) - second := provisionersdk.SessionDir(uuid.NewString()) + second := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, second, now.Add(-6*24*time.Hour)) // when @@ -77,7 +78,7 @@ func TestStaleSessions(t *testing.T) { entries, err := afero.ReadDir(fs, workDirectory) require.NoError(t, err) require.Len(t, entries, 1, "one session should be present") - require.Equal(t, second, entries[0].Name(), 1) + require.Equal(t, second.WorkDirectory(), filepath.Join(workDirectory, entries[0].Name()), 1) }) t.Run("no stale sessions", func(t *testing.T) { @@ -89,9 +90,9 @@ func TestStaleSessions(t *testing.T) { fs, logger := prepare() // given - first := provisionersdk.SessionDir(uuid.NewString()) + first := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, first, now.Add(-6*24*time.Hour)) - second := provisionersdk.SessionDir(uuid.NewString()) + second := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, second, now.Add(-5*24*time.Hour)) // when @@ -104,9 +105,10 @@ func TestStaleSessions(t *testing.T) { }) } -func addSessionFolder(t *testing.T, fs afero.Fs, sessionName string, modTime time.Time) { - err := fs.MkdirAll(filepath.Join(workDirectory, sessionName), 0o755) +func addSessionFolder(t *testing.T, fs afero.Fs, files tfpath.Layout, modTime time.Time) { + workdir := files.WorkDirectory() + err := fs.MkdirAll(workdir, 0o755) require.NoError(t, err, "can't create session folder") - require.NoError(t, fs.Chtimes(filepath.Join(workDirectory, sessionName), now, modTime), "can't chtime of session dir") + require.NoError(t, fs.Chtimes(workdir, now, modTime), "can't chtime of session dir") require.NoError(t, err, "can't set times") } diff --git a/provisionersdk/session.go b/provisionersdk/session.go index 3fd23628854e5..68a72190071d9 100644 --- a/provisionersdk/session.go +++ b/provisionersdk/session.go @@ -1,14 +1,10 @@ package provisionersdk import ( - "archive/tar" - "bytes" "context" "fmt" - "hash/crc32" "io" "os" - "path/filepath" "strings" "time" @@ -18,6 +14,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/codersdk/drpcsdk" + "github.com/coder/coder/v2/provisionersdk/tfpath" protobuf "google.golang.org/protobuf/proto" @@ -48,34 +45,15 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error err := CleanStaleSessions(s.Context(), p.opts.WorkDirectory, afero.NewOsFs(), time.Now(), s.Logger) if err != nil { - return xerrors.Errorf("unable to clean stale sessions %q: %w", s.WorkDirectory, err) + return xerrors.Errorf("unable to clean stale sessions %q: %w", s.Files, err) } - s.WorkDirectory = filepath.Join(p.opts.WorkDirectory, SessionDir(sessID)) - err = os.MkdirAll(s.WorkDirectory, 0o700) - if err != nil { - return xerrors.Errorf("create work directory %q: %w", s.WorkDirectory, err) - } + s.Files = tfpath.Session(p.opts.WorkDirectory, sessID) + defer func() { - var err error - // Cleanup the work directory after execution. - for attempt := 0; attempt < 5; attempt++ { - err = os.RemoveAll(s.WorkDirectory) - if err != nil { - // On Windows, open files cannot be removed. - // When the provisioner daemon is shutting down, - // it may take a few milliseconds for processes to exit. - // See: https://github.com/golang/go/issues/50510 - s.Logger.Debug(s.Context(), "failed to clean work directory; trying again", slog.Error(err)) - time.Sleep(250 * time.Millisecond) - continue - } - s.Logger.Debug(s.Context(), "cleaned up work directory") - return - } - s.Logger.Error(s.Context(), "failed to clean up work directory after multiple attempts", - slog.F("path", s.WorkDirectory), slog.Error(err)) + s.Files.Cleanup(s.Context(), s.Logger, afero.NewOsFs()) }() + req, err := stream.Recv() if err != nil { return xerrors.Errorf("receive config: %w", err) @@ -89,7 +67,7 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error s.logLevel = proto.LogLevel_value[strings.ToUpper(s.Config.ProvisionerLogLevel)] } - err = s.extractArchive() + err = s.Files.ExtractArchive(s.Context(), s.Logger, afero.NewOsFs(), s.Config) if err != nil { return xerrors.Errorf("extract archive: %w", err) } @@ -144,7 +122,7 @@ func (s *Session) handleRequests() error { return err } // Handle README centrally, so that individual provisioners don't need to mess with it. - readme, err := os.ReadFile(filepath.Join(s.WorkDirectory, ReadmeFile)) + readme, err := os.ReadFile(s.Files.ReadmeFilePath()) if err == nil { complete.Readme = readme } else { @@ -220,9 +198,9 @@ func (s *Session) handleRequests() error { } type Session struct { - Logger slog.Logger - WorkDirectory string - Config *proto.Config + Logger slog.Logger + Files tfpath.Layout + Config *proto.Config server Server stream proto.DRPCProvisioner_SessionStream @@ -233,92 +211,6 @@ func (s *Session) Context() context.Context { return s.stream.Context() } -func (s *Session) extractArchive() error { - ctx := s.Context() - - s.Logger.Info(ctx, "unpacking template source archive", - slog.F("size_bytes", len(s.Config.TemplateSourceArchive)), - ) - - reader := tar.NewReader(bytes.NewBuffer(s.Config.TemplateSourceArchive)) - // for safety, nil out the reference on Config, since the reader now owns it. - s.Config.TemplateSourceArchive = nil - for { - header, err := reader.Next() - if err != nil { - if xerrors.Is(err, io.EOF) { - break - } - return xerrors.Errorf("read template source archive: %w", err) - } - s.Logger.Debug(context.Background(), "read archive entry", - slog.F("name", header.Name), - slog.F("mod_time", header.ModTime), - slog.F("size", header.Size)) - - // Security: don't untar absolute or relative paths, as this can allow a malicious tar to overwrite - // files outside the workdir. - if !filepath.IsLocal(header.Name) { - return xerrors.Errorf("refusing to extract to non-local path") - } - // nolint: gosec - headerPath := filepath.Join(s.WorkDirectory, header.Name) - if !strings.HasPrefix(headerPath, filepath.Clean(s.WorkDirectory)) { - return xerrors.New("tar attempts to target relative upper directory") - } - mode := header.FileInfo().Mode() - if mode == 0 { - mode = 0o600 - } - - // Always check for context cancellation before reading the next header. - // This is mainly important for unit tests, since a canceled context means - // the underlying directory is going to be deleted. There still exists - // the small race condition that the context is canceled after this, and - // before the disk write. - if ctx.Err() != nil { - return xerrors.Errorf("context canceled: %w", ctx.Err()) - } - switch header.Typeflag { - case tar.TypeDir: - err = os.MkdirAll(headerPath, mode) - if err != nil { - return xerrors.Errorf("mkdir %q: %w", headerPath, err) - } - s.Logger.Debug(context.Background(), "extracted directory", - slog.F("path", headerPath), - slog.F("mode", fmt.Sprintf("%O", mode))) - case tar.TypeReg: - file, err := os.OpenFile(headerPath, os.O_CREATE|os.O_RDWR, mode) - if err != nil { - return xerrors.Errorf("create file %q (mode %s): %w", headerPath, mode, err) - } - - hash := crc32.NewIEEE() - hashReader := io.TeeReader(reader, hash) - // Max file size of 10MiB. - size, err := io.CopyN(file, hashReader, 10<<20) - if xerrors.Is(err, io.EOF) { - err = nil - } - if err != nil { - _ = file.Close() - return xerrors.Errorf("copy file %q: %w", headerPath, err) - } - err = file.Close() - if err != nil { - return xerrors.Errorf("close file %q: %s", headerPath, err) - } - s.Logger.Debug(context.Background(), "extracted file", - slog.F("size_bytes", size), - slog.F("path", headerPath), - slog.F("mode", mode), - slog.F("checksum", fmt.Sprintf("%x", hash.Sum(nil)))) - } - } - return nil -} - func (s *Session) ProvisionLog(level proto.LogLevel, output string) { if int32(level) < s.logLevel { return @@ -379,8 +271,3 @@ func (r *request[R, C]) do() (C, error) { return c, nil } } - -// SessionDir returns the directory name with mandatory prefix. -func SessionDir(sessID string) string { - return sessionDirPrefix + sessID -} diff --git a/provisionersdk/tfpath/tfpath.go b/provisionersdk/tfpath/tfpath.go new file mode 100644 index 0000000000000..57129e6242f75 --- /dev/null +++ b/provisionersdk/tfpath/tfpath.go @@ -0,0 +1,192 @@ +package tfpath + +import ( + "archive/tar" + "bytes" + "context" + "fmt" + "hash/crc32" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/spf13/afero" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "github.com/coder/coder/v2/provisionersdk/proto" +) + +const ( + // ReadmeFile is the location we look for to extract documentation from template versions. + ReadmeFile = "README.md" + + sessionDirPrefix = "Session" +) + +// Session creates a directory structure layout for terraform execution. The +// SessionID is a unique value for creating an ephemeral working directory inside +// the parentDirPath. All helper functions will return paths for various +// terraform asserts inside this working directory. +func Session(parentDirPath, sessionID string) Layout { + return Layout(filepath.Join(parentDirPath, sessionDirPrefix+sessionID)) +} + +// Layout is the terraform execution working directory structure. +// It also contains some methods for common file operations within that layout. +// Such as "Cleanup" and "ExtractArchive". +// TODO: Maybe we should include the afero.FS here as well, then all operations +// would be on the same FS? +type Layout string + +// WorkDirectory returns the root working directory for Terraform files. +func (l Layout) WorkDirectory() string { return string(l) } + +func (l Layout) StateFilePath() string { + return filepath.Join(l.WorkDirectory(), "terraform.tfstate") +} + +func (l Layout) PlanFilePath() string { + return filepath.Join(l.WorkDirectory(), "terraform.tfplan") +} + +func (l Layout) TerraformLockFile() string { + return filepath.Join(l.WorkDirectory(), ".terraform.lock.hcl") +} + +func (l Layout) ReadmeFilePath() string { + return filepath.Join(l.WorkDirectory(), ReadmeFile) +} + +func (l Layout) TerraformMetadataDir() string { + return filepath.Join(l.WorkDirectory(), ".terraform") +} + +func (l Layout) ModulesDirectory() string { + return filepath.Join(l.TerraformMetadataDir(), "modules") +} + +func (l Layout) ModulesFilePath() string { + return filepath.Join(l.ModulesDirectory(), "modules.json") +} + +func (l Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error { + logger.Info(ctx, "unpacking template source archive", + slog.F("size_bytes", len(cfg.TemplateSourceArchive)), + ) + + err := fs.MkdirAll(l.WorkDirectory(), 0o700) + if err != nil { + return xerrors.Errorf("create work directory %q: %w", l.WorkDirectory(), err) + } + + reader := tar.NewReader(bytes.NewBuffer(cfg.TemplateSourceArchive)) + // for safety, nil out the reference on Config, since the reader now owns it. + cfg.TemplateSourceArchive = nil + for { + header, err := reader.Next() + if err != nil { + if xerrors.Is(err, io.EOF) { + break + } + return xerrors.Errorf("read template source archive: %w", err) + } + logger.Debug(context.Background(), "read archive entry", + slog.F("name", header.Name), + slog.F("mod_time", header.ModTime), + slog.F("size", header.Size)) + + // Security: don't untar absolute or relative paths, as this can allow a malicious tar to overwrite + // files outside the workdir. + if !filepath.IsLocal(header.Name) { + return xerrors.Errorf("refusing to extract to non-local path") + } + + // nolint: gosec // TODO: Use relative paths inside the workdir only. + headerPath := filepath.Join(l.WorkDirectory(), header.Name) + if !strings.HasPrefix(headerPath, filepath.Clean(l.WorkDirectory())) { + return xerrors.New("tar attempts to target relative upper directory") + } + mode := header.FileInfo().Mode() + if mode == 0 { + mode = 0o600 + } + + // Always check for context cancellation before reading the next header. + // This is mainly important for unit tests, since a canceled context means + // the underlying directory is going to be deleted. There still exists + // the small race condition that the context is canceled after this, and + // before the disk write. + if ctx.Err() != nil { + return xerrors.Errorf("context canceled: %w", ctx.Err()) + } + switch header.Typeflag { + case tar.TypeDir: + err = fs.MkdirAll(headerPath, mode) + if err != nil { + return xerrors.Errorf("mkdir %q: %w", headerPath, err) + } + logger.Debug(context.Background(), "extracted directory", + slog.F("path", headerPath), + slog.F("mode", fmt.Sprintf("%O", mode))) + case tar.TypeReg: + file, err := fs.OpenFile(headerPath, os.O_CREATE|os.O_RDWR, mode) + if err != nil { + return xerrors.Errorf("create file %q (mode %s): %w", headerPath, mode, err) + } + + hash := crc32.NewIEEE() + hashReader := io.TeeReader(reader, hash) + // Max file size of 10MiB. + size, err := io.CopyN(file, hashReader, 10<<20) + if xerrors.Is(err, io.EOF) { + err = nil + } + if err != nil { + _ = file.Close() + return xerrors.Errorf("copy file %q: %w", headerPath, err) + } + err = file.Close() + if err != nil { + return xerrors.Errorf("close file %q: %s", headerPath, err) + } + logger.Debug(context.Background(), "extracted file", + slog.F("size_bytes", size), + slog.F("path", headerPath), + slog.F("mode", mode), + slog.F("checksum", fmt.Sprintf("%x", hash.Sum(nil)))) + } + } + + return nil +} + +// Cleanup removes the work directory and all of its contents. +func (l Layout) Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) { + var err error + path := l.WorkDirectory() + + for attempt := 0; attempt < 5; attempt++ { + err := fs.RemoveAll(path) + if err != nil { + // On Windows, open files cannot be removed. + // When the provisioner daemon is shutting down, + // it may take a few milliseconds for processes to exit. + // See: https://github.com/golang/go/issues/50510 + logger.Debug(ctx, "failed to clean work directory; trying again", slog.Error(err)) + // TODO: Should we abort earlier if the context is done? + time.Sleep(250 * time.Millisecond) + continue + } + logger.Debug(ctx, "cleaned up work directory") + return + } + + // Returning an error at this point cannot do any good. The caller cannot resolve + // this. There is a routine cleanup task that will remove old work directories + // when this fails. + logger.Error(ctx, "failed to clean up work directory after multiple attempts", + slog.F("path", path), slog.Error(err)) +} From c47b437c127e20bcbe40b411d453855c508d581b Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 12 Nov 2025 10:42:16 -0600 Subject: [PATCH 130/255] chore: comment no-lint on gosec for unsafe zip extracting (#20741) --- provisionersdk/tfpath/tfpath.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisionersdk/tfpath/tfpath.go b/provisionersdk/tfpath/tfpath.go index 57129e6242f75..8662a5f096950 100644 --- a/provisionersdk/tfpath/tfpath.go +++ b/provisionersdk/tfpath/tfpath.go @@ -104,7 +104,7 @@ func (l Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero return xerrors.Errorf("refusing to extract to non-local path") } - // nolint: gosec // TODO: Use relative paths inside the workdir only. + // nolint: gosec // Safe to no-lint because the filepath.IsLocal check above. headerPath := filepath.Join(l.WorkDirectory(), header.Name) if !strings.HasPrefix(headerPath, filepath.Clean(l.WorkDirectory())) { return xerrors.New("tar attempts to target relative upper directory") From 5e85663ce3872c41faa3e7ef314be4bac51b717d Mon Sep 17 00:00:00 2001 From: Zach <3724288+zedkipp@users.noreply.github.com> Date: Wed, 12 Nov 2025 10:48:19 -0700 Subject: [PATCH 131/255] feat(cli): add macOS support for session token keyring storage (#20613) Add support for storing the CLI session token in the OS keyring on macOS when the --use-keyring flag is provided. https://github.com/coder/coder/issues/19403 https://www.notion.so/coderhq/CLI-Session-Token-in-OS-Keyring-293d579be592808b8b7fd235304e50d5 --- cli/keyring_test.go | 6 +- cli/sessionstore/sessionstore.go | 6 + cli/sessionstore/sessionstore_darwin.go | 105 ++++++++++++++++++ cli/sessionstore/sessionstore_darwin_test.go | 34 ++++++ cli/sessionstore/sessionstore_other.go | 4 +- cli/sessionstore/sessionstore_other_test.go | 10 ++ cli/sessionstore/sessionstore_test.go | 70 +++++++++++- cli/sessionstore/sessionstore_windows.go | 6 - cli/sessionstore/sessionstore_windows_test.go | 75 ++----------- 9 files changed, 238 insertions(+), 78 deletions(-) create mode 100644 cli/sessionstore/sessionstore_darwin.go create mode 100644 cli/sessionstore/sessionstore_darwin_test.go create mode 100644 cli/sessionstore/sessionstore_other_test.go diff --git a/cli/keyring_test.go b/cli/keyring_test.go index 1a4e90895a6eb..646f4ae19a034 100644 --- a/cli/keyring_test.go +++ b/cli/keyring_test.go @@ -282,9 +282,9 @@ func TestUseKeyringUnsupportedOS(t *testing.T) { // a helpful error message. t.Parallel() - // Skip on Windows since the keyring is actually supported. - if runtime.GOOS == "windows" { - t.Skip("Skipping unsupported OS test on Windows where keyring is supported") + // Only run this on an unsupported OS. + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + t.Skipf("Skipping unsupported OS test on %s where keyring is supported", runtime.GOOS) } const expMessage = "keyring storage is not supported on this operating system; remove the --use-keyring flag" diff --git a/cli/sessionstore/sessionstore.go b/cli/sessionstore/sessionstore.go index 8ae14c2786c2f..029e86ad7e99b 100644 --- a/cli/sessionstore/sessionstore.go +++ b/cli/sessionstore/sessionstore.go @@ -46,6 +46,12 @@ var ( ErrNotImplemented = xerrors.New("not implemented") ) +const ( + // defaultServiceName is the service name used in keyrings for storing Coder CLI session + // tokens. + defaultServiceName = "coder-v2-credentials" +) + // keyringProvider represents an operating system keyring. The expectation // is these methods operate on the user/login keyring. type keyringProvider interface { diff --git a/cli/sessionstore/sessionstore_darwin.go b/cli/sessionstore/sessionstore_darwin.go new file mode 100644 index 0000000000000..be398d42e7049 --- /dev/null +++ b/cli/sessionstore/sessionstore_darwin.go @@ -0,0 +1,105 @@ +//go:build darwin + +package sessionstore + +import ( + "encoding/base64" + "fmt" + "io" + "os" + "os/exec" + "regexp" + "strings" +) + +const ( + // fixedUsername is the fixed username used for all keychain entries. + // Since our interface only uses service names, we use a constant username. + fixedUsername = "coder-login-credentials" + + execPathKeychain = "/usr/bin/security" + notFoundStr = "could not be found" +) + +// operatingSystemKeyring implements keyringProvider for macOS. +// It is largely adapted from the zalando/go-keyring package. +type operatingSystemKeyring struct{} + +func (operatingSystemKeyring) Set(service, credential string) error { + // if the added secret has multiple lines or some non ascii, + // macOS will hex encode it on return. To avoid getting garbage, we + // encode all passwords + password := base64.StdEncoding.EncodeToString([]byte(credential)) + + cmd := exec.Command(execPathKeychain, "-i") + stdIn, err := cmd.StdinPipe() + if err != nil { + return err + } + + if err = cmd.Start(); err != nil { + return err + } + + command := fmt.Sprintf("add-generic-password -U -s %s -a %s -w %s\n", + shellEscape(service), + shellEscape(fixedUsername), + shellEscape(password)) + if len(command) > 4096 { + return ErrSetDataTooBig + } + + if _, err := io.WriteString(stdIn, command); err != nil { + return err + } + + if err = stdIn.Close(); err != nil { + return err + } + + return cmd.Wait() +} + +func (operatingSystemKeyring) Get(service string) ([]byte, error) { + out, err := exec.Command( + execPathKeychain, + "find-generic-password", + "-s", service, + "-wa", fixedUsername).CombinedOutput() + if err != nil { + if strings.Contains(string(out), notFoundStr) { + return nil, os.ErrNotExist + } + return nil, err + } + + trimStr := strings.TrimSpace(string(out)) + return base64.StdEncoding.DecodeString(trimStr) +} + +func (operatingSystemKeyring) Delete(service string) error { + out, err := exec.Command( + execPathKeychain, + "delete-generic-password", + "-s", service, + "-a", fixedUsername).CombinedOutput() + if strings.Contains(string(out), notFoundStr) { + return os.ErrNotExist + } + return err +} + +// shellEscape returns a shell-escaped version of the string s. +// This is adapted from github.com/zalando/go-keyring/internal/shellescape. +func shellEscape(s string) string { + if len(s) == 0 { + return "''" + } + + pattern := regexp.MustCompile(`[^\w@%+=:,./-]`) + if pattern.MatchString(s) { + return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'" + } + + return s +} diff --git a/cli/sessionstore/sessionstore_darwin_test.go b/cli/sessionstore/sessionstore_darwin_test.go new file mode 100644 index 0000000000000..a90ee12d96cc1 --- /dev/null +++ b/cli/sessionstore/sessionstore_darwin_test.go @@ -0,0 +1,34 @@ +//go:build darwin + +package sessionstore_test + +import ( + "encoding/base64" + "os/exec" + "testing" +) + +const ( + execPathKeychain = "/usr/bin/security" + fixedUsername = "coder-login-credentials" +) + +func readRawKeychainCredential(t *testing.T, service string) []byte { + t.Helper() + + out, err := exec.Command( + execPathKeychain, + "find-generic-password", + "-s", service, + "-wa", fixedUsername).CombinedOutput() + if err != nil { + t.Fatal(err) + } + + dst := make([]byte, base64.StdEncoding.DecodedLen(len(out))) + n, err := base64.StdEncoding.Decode(dst, out) + if err != nil { + t.Fatal(err) + } + return dst[:n] +} diff --git a/cli/sessionstore/sessionstore_other.go b/cli/sessionstore/sessionstore_other.go index d930790135aca..a71458a360c94 100644 --- a/cli/sessionstore/sessionstore_other.go +++ b/cli/sessionstore/sessionstore_other.go @@ -1,9 +1,7 @@ -//go:build !windows +//go:build !windows && !darwin package sessionstore -const defaultServiceName = "not-implemented" - type operatingSystemKeyring struct{} func (operatingSystemKeyring) Set(_, _ string) error { diff --git a/cli/sessionstore/sessionstore_other_test.go b/cli/sessionstore/sessionstore_other_test.go new file mode 100644 index 0000000000000..b924a95d12897 --- /dev/null +++ b/cli/sessionstore/sessionstore_other_test.go @@ -0,0 +1,10 @@ +//go:build !windows && !darwin + +package sessionstore_test + +import "testing" + +func readRawKeychainCredential(t *testing.T, _ string) []byte { + t.Fatal("not implemented") + return nil +} diff --git a/cli/sessionstore/sessionstore_test.go b/cli/sessionstore/sessionstore_test.go index c01a1256eb085..1ecb0279918fd 100644 --- a/cli/sessionstore/sessionstore_test.go +++ b/cli/sessionstore/sessionstore_test.go @@ -1,6 +1,7 @@ package sessionstore_test import ( + "encoding/json" "errors" "fmt" "net/url" @@ -16,6 +17,11 @@ import ( "github.com/coder/coder/v2/cli/sessionstore" ) +type storedCredentials map[string]struct { + CoderURL string `json:"coder_url"` + APIToken string `json:"api_token"` +} + // Generate a test service name for use with the OS keyring. It uses a combination // of the test name and a nanosecond timestamp to prevent collisions. func keyringTestServiceName(t *testing.T) string { @@ -26,8 +32,8 @@ func keyringTestServiceName(t *testing.T) string { func TestKeyring(t *testing.T) { t.Parallel() - if runtime.GOOS != "windows" { - t.Skip("linux and darwin are not supported yet") + if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { + t.Skip("linux is not supported yet") } // This test exercises use of the operating system keyring. As a result, @@ -199,6 +205,66 @@ func TestKeyring(t *testing.T) { err = backend.Delete(srvURL2) require.NoError(t, err) }) + + t.Run("StorageFormat", func(t *testing.T) { + t.Parallel() + // The storage format must remain consistent to ensure we don't break + // compatibility with other Coder related applications that may read + // or decode the same credential. + + const testURL1 = "http://127.0.0.1:1337" + srv1URL, err := url.Parse(testURL1) + require.NoError(t, err) + + const testURL2 = "http://127.0.0.1:1338" + srv2URL, err := url.Parse(testURL2) + require.NoError(t, err) + + serviceName := keyringTestServiceName(t) + backend := sessionstore.NewKeyringWithService(serviceName) + t.Cleanup(func() { + _ = backend.Delete(srv1URL) + _ = backend.Delete(srv2URL) + }) + + // Write token for server 1 + const token1 = "token-server-1" + err = backend.Write(srv1URL, token1) + require.NoError(t, err) + + // Write token for server 2 (should NOT overwrite server 1's token) + const token2 = "token-server-2" + err = backend.Write(srv2URL, token2) + require.NoError(t, err) + + // Verify both credentials are stored in the raw format and can + // be extracted through the Backend API. + rawCredential := readRawKeychainCredential(t, serviceName) + + storedCreds := make(storedCredentials) + err = json.Unmarshal(rawCredential, &storedCreds) + require.NoError(t, err, "unmarshalling stored credentials") + + // Both credentials should exist + require.Len(t, storedCreds, 2) + require.Equal(t, token1, storedCreds[srv1URL.Host].APIToken) + require.Equal(t, token2, storedCreds[srv2URL.Host].APIToken) + + // Read individual credentials + token, err := backend.Read(srv1URL) + require.NoError(t, err) + require.Equal(t, token1, token) + + token, err = backend.Read(srv2URL) + require.NoError(t, err) + require.Equal(t, token2, token) + + // Cleanup + err = backend.Delete(srv1URL) + require.NoError(t, err) + err = backend.Delete(srv2URL) + require.NoError(t, err) + }) } func TestFile(t *testing.T) { diff --git a/cli/sessionstore/sessionstore_windows.go b/cli/sessionstore/sessionstore_windows.go index 6c48db0154f3f..3dd38c19da31d 100644 --- a/cli/sessionstore/sessionstore_windows.go +++ b/cli/sessionstore/sessionstore_windows.go @@ -10,12 +10,6 @@ import ( "github.com/danieljoos/wincred" ) -const ( - // defaultServiceName is the service name used in the Windows Credential Manager - // for storing Coder CLI session tokens. - defaultServiceName = "coder-v2-credentials" -) - // operatingSystemKeyring implements keyringProvider and uses Windows Credential Manager. // It is largely adapted from the zalando/go-keyring package. type operatingSystemKeyring struct{} diff --git a/cli/sessionstore/sessionstore_windows_test.go b/cli/sessionstore/sessionstore_windows_test.go index 4d90e27f5c132..ef643d3033dba 100644 --- a/cli/sessionstore/sessionstore_windows_test.go +++ b/cli/sessionstore/sessionstore_windows_test.go @@ -14,6 +14,16 @@ import ( "github.com/coder/coder/v2/cli/sessionstore" ) +func readRawKeychainCredential(t *testing.T, serviceName string) []byte { + t.Helper() + + winCred, err := wincred.GetGenericCredential(serviceName) + if err != nil { + t.Fatal(err) + } + return winCred.CredentialBlob +} + func TestWindowsKeyring_WriteReadDelete(t *testing.T) { t.Parallel() @@ -38,10 +48,7 @@ func TestWindowsKeyring_WriteReadDelete(t *testing.T) { winCred, err := wincred.GetGenericCredential(serviceName) require.NoError(t, err, "getting windows credential") - var storedCreds map[string]struct { - CoderURL string `json:"coder_url"` - APIToken string `json:"api_token"` - } + storedCreds := make(storedCredentials) err = json.Unmarshal(winCred.CredentialBlob, &storedCreds) require.NoError(t, err, "unmarshalling stored credentials") @@ -65,63 +72,3 @@ func TestWindowsKeyring_WriteReadDelete(t *testing.T) { _, err = backend.Read(srvURL) require.ErrorIs(t, err, os.ErrNotExist) } - -func TestWindowsKeyring_MultipleServers(t *testing.T) { - t.Parallel() - - const testURL1 = "http://127.0.0.1:1337" - srv1URL, err := url.Parse(testURL1) - require.NoError(t, err) - - const testURL2 = "http://127.0.0.1:1338" - srv2URL, err := url.Parse(testURL2) - require.NoError(t, err) - - serviceName := keyringTestServiceName(t) - backend := sessionstore.NewKeyringWithService(serviceName) - t.Cleanup(func() { - _ = backend.Delete(srv1URL) - _ = backend.Delete(srv2URL) - }) - - // Write token for server 1 - const token1 = "token-server-1" - err = backend.Write(srv1URL, token1) - require.NoError(t, err) - - // Write token for server 2 (should NOT overwrite server 1's token) - const token2 = "token-server-2" - err = backend.Write(srv2URL, token2) - require.NoError(t, err) - - // Verify both credentials are stored in Windows Credential Manager - winCred, err := wincred.GetGenericCredential(serviceName) - require.NoError(t, err, "getting windows credential") - - var storedCreds map[string]struct { - CoderURL string `json:"coder_url"` - APIToken string `json:"api_token"` - } - err = json.Unmarshal(winCred.CredentialBlob, &storedCreds) - require.NoError(t, err, "unmarshalling stored credentials") - - // Both credentials should exist - require.Len(t, storedCreds, 2) - require.Equal(t, token1, storedCreds[srv1URL.Host].APIToken) - require.Equal(t, token2, storedCreds[srv2URL.Host].APIToken) - - // Read individual credentials - token, err := backend.Read(srv1URL) - require.NoError(t, err) - require.Equal(t, token1, token) - - token, err = backend.Read(srv2URL) - require.NoError(t, err) - require.Equal(t, token2, token) - - // Cleanup - err = backend.Delete(srv1URL) - require.NoError(t, err) - err = backend.Delete(srv2URL) - require.NoError(t, err) -} From 9149c1e9f20d5a41338ae9d103737d8fadc5b29a Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 12 Nov 2025 12:46:39 -0600 Subject: [PATCH 132/255] chore: append template metadata to protobuf config (#20558) Adds some extra meta data sent to provisioners. Also adds a field `reuse_terraform_workspace` to tell the provisioner whether or not to use the caching experiment. --- ...oder_provisioner_list_--output_json.golden | 2 +- coderd/database/dbfake/dbfake.go | 1 + .../provisionerdserver/provisionerdserver.go | 27 +- .../provisionerdserver_test.go | 32 +- coderd/templateversions.go | 6 +- coderd/util/strings/strings.go | 9 + provisionerd/proto/provisionerd.pb.go | 528 +++++++++--------- provisionerd/proto/provisionerd.proto | 1 + provisionerd/proto/version.go | 7 +- provisionerd/runner/runner.go | 15 +- provisionersdk/proto/provisioner.pb.go | 527 +++++++++-------- provisionersdk/proto/provisioner.proto | 6 + site/e2e/provisionerGenerated.ts | 23 + 13 files changed, 663 insertions(+), 521 deletions(-) diff --git a/cli/testdata/coder_provisioner_list_--output_json.golden b/cli/testdata/coder_provisioner_list_--output_json.golden index 32de8cbd857f4..3749b159aeebf 100644 --- a/cli/testdata/coder_provisioner_list_--output_json.golden +++ b/cli/testdata/coder_provisioner_list_--output_json.golden @@ -7,7 +7,7 @@ "last_seen_at": "====[timestamp]=====", "name": "test-daemon", "version": "v0.0.0-devel", - "api_version": "1.11", + "api_version": "1.12", "provisioners": [ "echo" ], diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 45a441bbe9486..ba430cd57e7ca 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -622,6 +622,7 @@ func (t TemplateVersionBuilder) Do() TemplateVersionResponse { } payload, err := json.Marshal(provisionerdserver.TemplateVersionImportJob{ + TemplateID: t.seed.TemplateID, TemplateVersionID: t.seed.ID, }) require.NoError(t.t, err) diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index ae1338722aa2d..dec8ab6d38424 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -43,6 +43,7 @@ import ( "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/usage" "github.com/coder/coder/v2/coderd/usage/usagetypes" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/coderd/wspubsub" "github.com/coder/coder/v2/codersdk" @@ -697,13 +698,14 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo protoJob.Type = &proto.AcquiredJob_WorkspaceBuild_{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ - WorkspaceBuildId: workspaceBuild.ID.String(), - WorkspaceName: workspace.Name, - State: workspaceBuild.ProvisionerState, - RichParameterValues: convertRichParameterValues(workspaceBuildParameters), - PreviousParameterValues: convertRichParameterValues(lastWorkspaceBuildParameters), - VariableValues: asVariableValues(templateVariables), - ExternalAuthProviders: externalAuthProviders, + WorkspaceBuildId: workspaceBuild.ID.String(), + WorkspaceName: workspace.Name, + State: workspaceBuild.ProvisionerState, + RichParameterValues: convertRichParameterValues(workspaceBuildParameters), + PreviousParameterValues: convertRichParameterValues(lastWorkspaceBuildParameters), + VariableValues: asVariableValues(templateVariables), + ExternalAuthProviders: externalAuthProviders, + ExpReuseTerraformWorkspace: ptr.Ref(false), // TODO: Toggle based on experiment Metadata: &sdkproto.Metadata{ CoderUrl: s.AccessURL.String(), WorkspaceTransition: transition, @@ -773,6 +775,11 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo return nil, failJob(err.Error()) } + templateID := "" + if input.TemplateID.Valid { + templateID = input.TemplateID.UUID.String() + } + protoJob.Type = &proto.AcquiredJob_TemplateImport_{ TemplateImport: &proto.AcquiredJob_TemplateImport{ UserVariableValues: convertVariableValues(userVariableValues), @@ -781,6 +788,8 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo // There is no owner for a template import, but we can assume // the "Everyone" group as a placeholder. WorkspaceOwnerGroups: []string{database.EveryoneGroup}, + TemplateId: templateID, + TemplateVersionId: input.TemplateVersionID.String(), }, }, } @@ -3210,6 +3219,10 @@ func auditActionFromTransition(transition database.WorkspaceTransition) database } type TemplateVersionImportJob struct { + // TemplateID is not guaranteed to be set. Template versions can be created + // without being associated with a template. Resulting in a template id of + // `uuid.Nil` + TemplateID uuid.NullUUID `json:"template_id"` TemplateVersionID uuid.UUID `json:"template_version_id"` UserVariableValues []codersdk.VariableValue `json:"user_variable_values"` } diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index a41940d77dbe9..d4597f6546978 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -26,6 +26,7 @@ import ( "storj.io/drpc" "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/quartz" "github.com/coder/serpent" @@ -59,24 +60,24 @@ import ( ) func testTemplateScheduleStore() *atomic.Pointer[schedule.TemplateScheduleStore] { - ptr := &atomic.Pointer[schedule.TemplateScheduleStore]{} + poitr := &atomic.Pointer[schedule.TemplateScheduleStore]{} store := schedule.NewAGPLTemplateScheduleStore() - ptr.Store(&store) - return ptr + poitr.Store(&store) + return poitr } func testUserQuietHoursScheduleStore() *atomic.Pointer[schedule.UserQuietHoursScheduleStore] { - ptr := &atomic.Pointer[schedule.UserQuietHoursScheduleStore]{} + poitr := &atomic.Pointer[schedule.UserQuietHoursScheduleStore]{} store := schedule.NewAGPLUserQuietHoursScheduleStore() - ptr.Store(&store) - return ptr + poitr.Store(&store) + return poitr } func testUsageInserter() *atomic.Pointer[usage.Inserter] { - ptr := &atomic.Pointer[usage.Inserter]{} + poitr := &atomic.Pointer[usage.Inserter]{} inserter := usage.NewAGPLInserter() - ptr.Store(&inserter) - return ptr + poitr.Store(&inserter) + return poitr } func TestAcquireJob_LongPoll(t *testing.T) { @@ -474,8 +475,9 @@ func TestAcquireJob(t *testing.T) { }) want, err := json.Marshal(&proto.AcquiredJob_WorkspaceBuild_{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ - WorkspaceBuildId: build.ID.String(), - WorkspaceName: workspace.Name, + ExpReuseTerraformWorkspace: ptr.Ref(false), + WorkspaceBuildId: build.ID.String(), + WorkspaceName: workspace.Name, VariableValues: []*sdkproto.VariableValue{ { Name: "first", @@ -629,6 +631,7 @@ func TestAcquireJob(t *testing.T) { Metadata: &sdkproto.Metadata{ CoderUrl: (&url.URL{}).String(), WorkspaceOwnerGroups: []string{database.EveryoneGroup}, + TemplateVersionId: uuid.Nil.String(), }, }, }) @@ -677,6 +680,7 @@ func TestAcquireJob(t *testing.T) { Metadata: &sdkproto.Metadata{ CoderUrl: (&url.URL{}).String(), WorkspaceOwnerGroups: []string{database.EveryoneGroup}, + TemplateVersionId: version.ID.String(), }, }, }) @@ -4392,11 +4396,11 @@ type fakeUsageInserter struct { var _ usage.Inserter = &fakeUsageInserter{} func newFakeUsageInserter() (*fakeUsageInserter, *atomic.Pointer[usage.Inserter]) { - ptr := &atomic.Pointer[usage.Inserter]{} + poitr := &atomic.Pointer[usage.Inserter]{} fake := &fakeUsageInserter{} var inserter usage.Inserter = fake - ptr.Store(&inserter) - return fake, ptr + poitr.Store(&inserter) + return fake, poitr } func (f *fakeUsageInserter) InsertDiscreteUsageEvent(_ context.Context, _ database.Store, event usagetypes.DiscreteEvent) error { diff --git a/coderd/templateversions.go b/coderd/templateversions.go index 2e959702fbde5..13dd93d528793 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -1609,9 +1609,13 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht var matchedProvisioners codersdk.MatchedProvisioners err = api.Database.InTx(func(tx database.Store) error { jobID := uuid.New() - templateVersionID := uuid.New() + jobInput, err := json.Marshal(provisionerdserver.TemplateVersionImportJob{ + TemplateID: uuid.NullUUID{ + UUID: req.TemplateID, + Valid: req.TemplateID != uuid.Nil, + }, TemplateVersionID: templateVersionID, UserVariableValues: req.UserVariableValues, }) diff --git a/coderd/util/strings/strings.go b/coderd/util/strings/strings.go index e21908d488cd8..f320142da55a1 100644 --- a/coderd/util/strings/strings.go +++ b/coderd/util/strings/strings.go @@ -10,6 +10,15 @@ import ( "github.com/microcosm-cc/bluemonday" ) +// EmptyToNil returns a `nil` for an empty string, or a pointer to the string +// otherwise. Useful when needing to treat zero values as nil in APIs. +func EmptyToNil(s string) *string { + if s == "" { + return nil + } + return &s +} + // JoinWithConjunction joins a slice of strings with commas except for the last // two which are joined with "and". func JoinWithConjunction(s []string) string { diff --git a/provisionerd/proto/provisionerd.pb.go b/provisionerd/proto/provisionerd.pb.go index 818719f1b3995..e66e1a33de1f4 100644 --- a/provisionerd/proto/provisionerd.pb.go +++ b/provisionerd/proto/provisionerd.pb.go @@ -952,7 +952,8 @@ type AcquiredJob_WorkspaceBuild struct { // previous_parameter_values is used to pass the values of the previous // workspace build. Omit these values if the workspace is being created // for the first time. - PreviousParameterValues []*proto.RichParameterValue `protobuf:"bytes,10,rep,name=previous_parameter_values,json=previousParameterValues,proto3" json:"previous_parameter_values,omitempty"` + PreviousParameterValues []*proto.RichParameterValue `protobuf:"bytes,10,rep,name=previous_parameter_values,json=previousParameterValues,proto3" json:"previous_parameter_values,omitempty"` + ExpReuseTerraformWorkspace *bool `protobuf:"varint,11,opt,name=exp_reuse_terraform_workspace,json=expReuseTerraformWorkspace,proto3,oneof" json:"exp_reuse_terraform_workspace,omitempty"` } func (x *AcquiredJob_WorkspaceBuild) Reset() { @@ -1050,6 +1051,13 @@ func (x *AcquiredJob_WorkspaceBuild) GetPreviousParameterValues() []*proto.RichP return nil } +func (x *AcquiredJob_WorkspaceBuild) GetExpReuseTerraformWorkspace() bool { + if x != nil && x.ExpReuseTerraformWorkspace != nil { + return *x.ExpReuseTerraformWorkspace + } + return false +} + type AcquiredJob_TemplateImport struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1593,7 +1601,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x1a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, - 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xf9, 0x0b, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69, + 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xe3, 0x0c, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, @@ -1626,7 +1634,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xa3, 0x04, 0x0a, 0x0e, 0x57, 0x6f, 0x72, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x8d, 0x05, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, @@ -1660,264 +1668,271 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x17, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x1a, 0x91, - 0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x4c, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, - 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, - 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x1a, 0xe3, 0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, - 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, - 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, - 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, - 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x1a, 0x40, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63, - 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x22, 0xd4, 0x03, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, - 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x51, 0x0a, - 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, - 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, - 0x12, 0x51, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, - 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x12, 0x52, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, - 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, - 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, - 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x55, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2d, - 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, - 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x10, 0x0a, - 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, - 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, - 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xbb, 0x0b, 0x0a, 0x0c, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, + 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x46, 0x0a, 0x1d, 0x65, 0x78, 0x70, 0x5f, + 0x72, 0x65, 0x75, 0x73, 0x65, 0x5f, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x5f, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x48, + 0x00, 0x52, 0x1a, 0x65, 0x78, 0x70, 0x52, 0x65, 0x75, 0x73, 0x65, 0x54, 0x65, 0x72, 0x72, 0x61, + 0x66, 0x6f, 0x72, 0x6d, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x88, 0x01, 0x01, + 0x42, 0x20, 0x0a, 0x1e, 0x5f, 0x65, 0x78, 0x70, 0x5f, 0x72, 0x65, 0x75, 0x73, 0x65, 0x5f, 0x74, + 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x1a, 0x91, 0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4c, + 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, + 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xe3, 0x01, 0x0a, + 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, + 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, + 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x01, + 0x10, 0x02, 0x1a, 0x40, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd4, 0x03, 0x0a, + 0x09, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, - 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, - 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, - 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x51, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x51, 0x0a, 0x0f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x55, 0x0a, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x52, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, - 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, - 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, - 0x79, 0x52, 0x75, 0x6e, 0x1a, 0xc0, 0x02, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, - 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, - 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, - 0x12, 0x55, 0x0a, 0x15, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x70, - 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, - 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x69, 0x5f, 0x74, 0x61, - 0x73, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x49, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x07, - 0x61, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x1a, 0xcf, 0x05, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, - 0x6f, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, - 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x41, 0x0a, - 0x1d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x1a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, - 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, - 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, - 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x73, 0x12, 0x38, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x6f, 0x64, - 0x75, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, - 0x0c, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x36, 0x0a, - 0x0c, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, + 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, + 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, + 0x1a, 0x55, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, + 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, + 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x22, 0xbb, 0x0b, 0x0a, 0x0c, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, + 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, + 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x55, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, + 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0xc0, + 0x02, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, + 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, + 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x55, 0x0a, 0x15, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x0b, 0x73, 0x74, 0x6f, 0x70, 0x4d, 0x6f, - 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, - 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x52, 0x07, 0x70, 0x72, 0x65, - 0x73, 0x65, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x6f, 0x64, 0x75, - 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, - 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, - 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, - 0x6c, 0x65, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x61, 0x73, 0x5f, 0x61, - 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x68, - 0x61, 0x73, 0x41, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x68, 0x61, 0x73, - 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x73, 0x45, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x74, 0x0a, 0x0e, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x33, 0x0a, 0x09, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x42, - 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xb0, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, - 0x2f, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, - 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, - 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0xa6, 0x03, 0x0a, 0x10, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x72, 0x2e, 0x41, 0x49, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x07, 0x61, 0x69, 0x54, 0x61, 0x73, 0x6b, + 0x73, 0x1a, 0xcf, 0x05, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x1d, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1a, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x38, 0x0a, + 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x0c, 0x73, 0x74, 0x6f, 0x70, 0x5f, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x52, 0x0b, 0x73, 0x74, 0x6f, 0x70, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, + 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x72, 0x65, 0x73, 0x65, 0x74, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, + 0x61, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, + 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x61, 0x73, 0x5f, 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, + 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x68, 0x61, 0x73, 0x41, 0x69, 0x54, 0x61, + 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x68, 0x61, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x11, 0x68, 0x61, 0x73, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x67, 0x65, + 0x6e, 0x74, 0x73, 0x1a, 0x74, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, + 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, + 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x22, 0xb0, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x22, 0xa6, 0x03, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, + 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, + 0x12, 0x25, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, + 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, + 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x12, 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x7a, 0x0a, + 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x43, + 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x4a, 0x0a, 0x12, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x4c, 0x0a, - 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, - 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x14, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x61, - 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, - 0x64, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, - 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, - 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, - 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, - 0x03, 0x10, 0x04, 0x22, 0x7a, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x63, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, - 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, - 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, - 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, - 0x4a, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x22, 0x68, 0x0a, 0x13, 0x43, - 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, - 0x6f, 0x6b, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x5f, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x63, 0x72, - 0x65, 0x64, 0x69, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, - 0x75, 0x64, 0x67, 0x65, 0x74, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, - 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x6c, 0x6f, 0x61, - 0x64, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0b, - 0x64, 0x61, 0x74, 0x61, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x44, 0x61, 0x74, 0x61, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x61, - 0x74, 0x61, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3a, 0x0a, 0x0b, 0x63, 0x68, 0x75, 0x6e, - 0x6b, 0x5f, 0x70, 0x69, 0x65, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x68, 0x75, 0x6e, - 0x6b, 0x50, 0x69, 0x65, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x50, - 0x69, 0x65, 0x63, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x34, 0x0a, 0x09, - 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x52, 0x4f, - 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x45, 0x4d, 0x4f, 0x4e, 0x10, - 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, - 0x10, 0x01, 0x32, 0x8b, 0x04, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0a, 0x41, 0x63, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, - 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x52, 0x0a, 0x14, 0x41, - 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x57, 0x69, 0x74, 0x68, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x64, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, - 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x28, 0x01, 0x30, 0x01, 0x12, - 0x52, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x12, 0x20, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, - 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, - 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, - 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x44, 0x0a, 0x0a, 0x55, 0x70, - 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, - 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, - 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, + 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, + 0x79, 0x43, 0x6f, 0x73, 0x74, 0x22, 0x68, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, + 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, + 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x29, 0x0a, 0x10, + 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x43, + 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x22, + 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x22, 0x93, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x75, + 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x55, 0x70, + 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x12, 0x3a, 0x0a, 0x0b, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x70, 0x69, 0x65, 0x63, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x50, 0x69, 0x65, 0x63, 0x65, + 0x48, 0x00, 0x52, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x50, 0x69, 0x65, 0x63, 0x65, 0x42, 0x06, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x34, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, + 0x45, 0x52, 0x5f, 0x44, 0x41, 0x45, 0x4d, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x50, + 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x10, 0x01, 0x32, 0x8b, 0x04, 0x0a, + 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d, + 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0a, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, + 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, + 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x52, 0x0a, 0x14, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x4a, 0x6f, 0x62, 0x57, 0x69, 0x74, 0x68, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x1b, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x28, 0x01, 0x30, 0x01, 0x12, 0x52, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, + 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, + 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, + 0x61, 0x69, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, + 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, + 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x44, 0x0a, 0x0a, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, + 0x6c, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -2307,6 +2322,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { (*UploadFileRequest_DataUpload)(nil), (*UploadFileRequest_ChunkPiece)(nil), } + file_provisionerd_proto_provisionerd_proto_msgTypes[11].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/provisionerd/proto/provisionerd.proto b/provisionerd/proto/provisionerd.proto index b008da33ea87e..5c54a600c0c1a 100644 --- a/provisionerd/proto/provisionerd.proto +++ b/provisionerd/proto/provisionerd.proto @@ -26,6 +26,7 @@ message AcquiredJob { // workspace build. Omit these values if the workspace is being created // for the first time. repeated provisioner.RichParameterValue previous_parameter_values = 10; + optional bool exp_reuse_terraform_workspace = 11; } message TemplateImport { provisioner.Metadata metadata = 1; diff --git a/provisionerd/proto/version.go b/provisionerd/proto/version.go index a7ea326d0f466..0c23b3939d4f2 100644 --- a/provisionerd/proto/version.go +++ b/provisionerd/proto/version.go @@ -57,9 +57,14 @@ import "github.com/coder/coder/v2/apiversion" // API v1.11: // - Added new fields `task_id` and `task_prompt` to `Manifest`. // - Added new field `app_id` to `AITask` +// +// API v1.12: +// - Added new field `template_version_id` to `provisioner.Metadata` +// - Added new field `exp_reuse_terraform_workspace` to `provisioner.Job.WorkspaceBuild` +// - Added fields `template_version_id`, `template_id`, and `exp_reuse_terraform_workspace` to `provisioner.Config` const ( CurrentMajor = 1 - CurrentMinor = 11 + CurrentMinor = 12 ) // CurrentVersion is the current provisionerd API version. diff --git a/provisionerd/runner/runner.go b/provisionerd/runner/runner.go index 924f0628820ce..22b6403fe729d 100644 --- a/provisionerd/runner/runner.go +++ b/provisionerd/runner/runner.go @@ -21,6 +21,7 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + strings2 "github.com/coder/coder/v2/coderd/util/strings" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/util/ptr" @@ -514,7 +515,10 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p defer span.End() failedJob := r.configure(&sdkproto.Config{ - TemplateSourceArchive: r.job.GetTemplateSourceArchive(), + TemplateSourceArchive: r.job.GetTemplateSourceArchive(), + TemplateId: strings2.EmptyToNil(r.job.GetTemplateImport().Metadata.TemplateId), + TemplateVersionId: strings2.EmptyToNil(r.job.GetTemplateImport().Metadata.TemplateVersionId), + ExpReuseTerraformWorkspace: ptr.Ref(false), }) if failedJob != nil { return nil, failedJob @@ -1010,9 +1014,12 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p } failedJob := r.configure(&sdkproto.Config{ - TemplateSourceArchive: r.job.GetTemplateSourceArchive(), - State: r.job.GetWorkspaceBuild().State, - ProvisionerLogLevel: r.job.GetWorkspaceBuild().LogLevel, + TemplateSourceArchive: r.job.GetTemplateSourceArchive(), + State: r.job.GetWorkspaceBuild().State, + ProvisionerLogLevel: r.job.GetWorkspaceBuild().LogLevel, + TemplateId: strings2.EmptyToNil(r.job.GetWorkspaceBuild().Metadata.TemplateId), + TemplateVersionId: strings2.EmptyToNil(r.job.GetWorkspaceBuild().Metadata.TemplateVersionId), + ExpReuseTerraformWorkspace: r.job.GetWorkspaceBuild().ExpReuseTerraformWorkspace, }) if failedJob != nil { return nil, failedJob diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index b884f5a21aca6..72741a1036b41 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -2946,6 +2946,7 @@ type Metadata struct { RunningAgentAuthTokens []*RunningAgentAuthToken `protobuf:"bytes,21,rep,name=running_agent_auth_tokens,json=runningAgentAuthTokens,proto3" json:"running_agent_auth_tokens,omitempty"` TaskId string `protobuf:"bytes,22,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` TaskPrompt string `protobuf:"bytes,23,opt,name=task_prompt,json=taskPrompt,proto3" json:"task_prompt,omitempty"` + TemplateVersionId string `protobuf:"bytes,24,opt,name=template_version_id,json=templateVersionId,proto3" json:"template_version_id,omitempty"` } func (x *Metadata) Reset() { @@ -3141,6 +3142,13 @@ func (x *Metadata) GetTaskPrompt() string { return "" } +func (x *Metadata) GetTemplateVersionId() string { + if x != nil { + return x.TemplateVersionId + } + return "" +} + // Config represents execution configuration shared by all subsequent requests in the Session type Config struct { state protoimpl.MessageState @@ -3152,6 +3160,11 @@ type Config struct { // state is the provisioner state (if any) State []byte `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` ProvisionerLogLevel string `protobuf:"bytes,3,opt,name=provisioner_log_level,json=provisionerLogLevel,proto3" json:"provisioner_log_level,omitempty"` + // Template imports can omit template id + TemplateId *string `protobuf:"bytes,4,opt,name=template_id,json=templateId,proto3,oneof" json:"template_id,omitempty"` + // Dry runs omit version id + TemplateVersionId *string `protobuf:"bytes,5,opt,name=template_version_id,json=templateVersionId,proto3,oneof" json:"template_version_id,omitempty"` + ExpReuseTerraformWorkspace *bool `protobuf:"varint,6,opt,name=exp_reuse_terraform_workspace,json=expReuseTerraformWorkspace,proto3,oneof" json:"exp_reuse_terraform_workspace,omitempty"` // Whether to reuse existing terraform workspaces if they exist. } func (x *Config) Reset() { @@ -3207,6 +3220,27 @@ func (x *Config) GetProvisionerLogLevel() string { return "" } +func (x *Config) GetTemplateId() string { + if x != nil && x.TemplateId != nil { + return *x.TemplateId + } + return "" +} + +func (x *Config) GetTemplateVersionId() string { + if x != nil && x.TemplateVersionId != nil { + return *x.TemplateVersionId + } + return "" +} + +func (x *Config) GetExpReuseTerraformWorkspace() bool { + if x != nil && x.ExpReuseTerraformWorkspace != nil { + return *x.ExpReuseTerraformWorkspace + } + return false +} + // ParseRequest consumes source-code to produce inputs. type ParseRequest struct { state protoimpl.MessageState @@ -4766,7 +4800,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x62, 0x61, 0x72, 0x41, 0x70, 0x70, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x69, 0x64, 0x65, 0x62, 0x61, 0x72, 0x41, 0x70, 0x70, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x70, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64, 0x42, 0x0e, - 0x0a, 0x0c, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x62, 0x61, 0x72, 0x5f, 0x61, 0x70, 0x70, 0x22, 0x84, + 0x0a, 0x0c, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x62, 0x61, 0x72, 0x5f, 0x61, 0x70, 0x70, 0x22, 0xb4, 0x0a, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, @@ -4847,7 +4881,10 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x73, 0x6b, 0x50, - 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x18, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xf7, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, @@ -4856,242 +4893,257 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0xa3, 0x02, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, - 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, - 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, - 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, - 0x12, 0x54, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, - 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, - 0x6c, 0x65, 0x74, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, - 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbe, 0x03, 0x0a, 0x0b, 0x50, 0x6c, 0x61, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, 0x0a, 0x15, 0x72, - 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, - 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x59, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, - 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, - 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, - 0x12, 0x5b, 0x0a, 0x19, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x70, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x17, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x2a, 0x0a, - 0x11, 0x6f, 0x6d, 0x69, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, - 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6f, 0x6d, 0x69, 0x74, 0x4d, 0x6f, - 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xc1, 0x05, 0x0a, 0x0c, 0x50, 0x6c, - 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, - 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, - 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, - 0x6e, 0x67, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, - 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, - 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x55, 0x0a, 0x15, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0a, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, - 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, - 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, - 0x2a, 0x0a, 0x11, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x75, - 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0c, 0x68, - 0x61, 0x73, 0x5f, 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x68, 0x61, 0x73, 0x41, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x0a, - 0x08, 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x49, - 0x54, 0x61, 0x73, 0x6b, 0x52, 0x07, 0x61, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x0a, - 0x13, 0x68, 0x61, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x73, 0x45, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x41, 0x0a, - 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x22, 0xee, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, - 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x65, 0x6c, 0x12, 0x24, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x33, 0x0a, 0x13, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x46, 0x0a, + 0x1d, 0x65, 0x78, 0x70, 0x5f, 0x72, 0x65, 0x75, 0x73, 0x65, 0x5f, 0x74, 0x65, 0x72, 0x72, 0x61, + 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, 0x1a, 0x65, 0x78, 0x70, 0x52, 0x65, 0x75, 0x73, 0x65, + 0x54, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x5f, 0x69, 0x64, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x42, 0x20, 0x0a, + 0x1e, 0x5f, 0x65, 0x78, 0x70, 0x5f, 0x72, 0x65, 0x75, 0x73, 0x65, 0x5f, 0x74, 0x65, 0x72, 0x72, + 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, + 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0xa3, 0x02, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x54, 0x0a, + 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, + 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbe, 0x03, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, + 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, - 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, - 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, - 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x07, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x41, 0x49, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x07, 0x61, 0x69, 0x54, 0x61, 0x73, 0x6b, - 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, - 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x2e, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, - 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x0f, - 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, - 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, - 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, - 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, - 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, - 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, - 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xc9, - 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, - 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, - 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, - 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, - 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, - 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, - 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x3a, 0x0a, 0x0b, 0x64, 0x61, - 0x74, 0x61, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x61, - 0x74, 0x61, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, - 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3a, 0x0a, 0x0b, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, - 0x70, 0x69, 0x65, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x50, - 0x69, 0x65, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x50, 0x69, 0x65, - 0x63, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x9c, 0x01, 0x0a, 0x0a, 0x44, - 0x61, 0x74, 0x61, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, - 0x61, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x75, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, - 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x22, 0x67, 0x0a, 0x0a, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x50, 0x69, 0x65, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x66, - 0x75, 0x6c, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x48, 0x61, 0x73, - 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x2a, 0xa8, 0x01, 0x0a, 0x11, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x46, 0x6f, 0x72, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, - 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x41, 0x44, 0x49, 0x4f, 0x10, 0x02, - 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x52, 0x4f, 0x50, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x12, 0x09, - 0x0a, 0x05, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x45, 0x58, - 0x54, 0x41, 0x52, 0x45, 0x41, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x4c, 0x49, 0x44, 0x45, - 0x52, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x42, 0x4f, 0x58, 0x10, - 0x07, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x57, 0x49, 0x54, 0x43, 0x48, 0x10, 0x08, 0x12, 0x0d, 0x0a, - 0x09, 0x54, 0x41, 0x47, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x10, 0x09, 0x12, 0x0f, 0x0a, 0x0b, - 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x10, 0x0a, 0x2a, 0x3f, 0x0a, - 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, - 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, - 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, - 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, - 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, - 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x09, 0x41, - 0x70, 0x70, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x0e, 0x0a, 0x06, 0x57, 0x49, 0x4e, 0x44, - 0x4f, 0x57, 0x10, 0x00, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x4c, 0x49, 0x4d, - 0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x41, 0x42, - 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, - 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, - 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x2a, 0x3e, 0x0a, 0x1b, 0x50, - 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, - 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, - 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x0b, 0x54, - 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, - 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, - 0x45, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, - 0x10, 0x02, 0x2a, 0x47, 0x0a, 0x0e, 0x44, 0x61, 0x74, 0x61, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, - 0x18, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x4f, 0x44, - 0x55, 0x4c, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x53, 0x10, 0x01, 0x32, 0x49, 0x0a, 0x0b, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, - 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, + 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x12, 0x59, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, + 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x5b, 0x0a, + 0x19, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, + 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x17, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x6d, + 0x69, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6f, 0x6d, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xc1, 0x05, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x6e, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, + 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, + 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, + 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, + 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x72, 0x65, 0x73, 0x65, 0x74, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, + 0x61, 0x6e, 0x12, 0x55, 0x0a, 0x15, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, + 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x6f, 0x64, + 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, + 0x69, 0x6c, 0x65, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x61, 0x73, 0x5f, + 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x68, 0x61, 0x73, 0x41, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x69, + 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x49, 0x54, 0x61, 0x73, + 0x6b, 0x52, 0x07, 0x61, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x68, 0x61, + 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x73, 0x45, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70, + 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xee, 0x02, + 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, + 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2e, + 0x0a, 0x08, 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, + 0x49, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x07, 0x61, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x22, 0xfa, + 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, + 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x43, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, 0x0a, + 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, 0x6c, + 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, 0x70, + 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, + 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xc9, 0x02, 0x0a, 0x08, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x32, + 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, + 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, 0x70, + 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, + 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x3a, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, + 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x55, + 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x55, 0x70, 0x6c, + 0x6f, 0x61, 0x64, 0x12, 0x3a, 0x0a, 0x0b, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x70, 0x69, 0x65, + 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x50, 0x69, 0x65, 0x63, + 0x65, 0x48, 0x00, 0x52, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x50, 0x69, 0x65, 0x63, 0x65, 0x42, + 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x9c, 0x01, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, + 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x55, 0x70, + 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, + 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x22, 0x67, 0x0a, 0x0a, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x50, + 0x69, 0x65, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x75, 0x6c, 0x6c, + 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1f, + 0x0a, 0x0b, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x2a, + 0xa8, 0x01, 0x0a, 0x11, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x46, 0x6f, 0x72, + 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, + 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x41, 0x44, 0x49, 0x4f, 0x10, 0x02, 0x12, 0x0c, 0x0a, + 0x08, 0x44, 0x52, 0x4f, 0x50, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x49, + 0x4e, 0x50, 0x55, 0x54, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x45, 0x58, 0x54, 0x41, 0x52, + 0x45, 0x41, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x4c, 0x49, 0x44, 0x45, 0x52, 0x10, 0x06, + 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x42, 0x4f, 0x58, 0x10, 0x07, 0x12, 0x0a, + 0x0a, 0x06, 0x53, 0x57, 0x49, 0x54, 0x43, 0x48, 0x10, 0x08, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x41, + 0x47, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x10, 0x09, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x55, 0x4c, + 0x54, 0x49, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x10, 0x0a, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, + 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, + 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, + 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, + 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, + 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, + 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, + 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, + 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x4f, + 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x0e, 0x0a, 0x06, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x10, + 0x00, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x4c, 0x49, 0x4d, 0x5f, 0x57, 0x49, + 0x4e, 0x44, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x41, 0x42, 0x10, 0x02, 0x2a, + 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, + 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, + 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x2a, 0x3e, 0x0a, 0x1b, 0x50, 0x72, 0x65, 0x62, + 0x75, 0x69, 0x6c, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, + 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x09, 0x0a, + 0x05, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x0b, 0x54, 0x69, 0x6d, 0x69, + 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x41, 0x52, 0x54, + 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, + 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, + 0x47, 0x0a, 0x0e, 0x44, 0x61, 0x74, 0x61, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, + 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x45, + 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x53, 0x10, 0x01, 0x32, 0x49, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, + 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, + 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5845,6 +5897,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { (*Agent_InstanceId)(nil), } file_provisionersdk_proto_provisioner_proto_msgTypes[32].OneofWrappers = []interface{}{} + file_provisionersdk_proto_provisioner_proto_msgTypes[34].OneofWrappers = []interface{}{} file_provisionersdk_proto_provisioner_proto_msgTypes[43].OneofWrappers = []interface{}{ (*Request_Config)(nil), (*Request_Parse)(nil), diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 803f3e2197ecd..89a69ce7022ca 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -364,6 +364,7 @@ message Metadata { repeated RunningAgentAuthToken running_agent_auth_tokens = 21; string task_id = 22; string task_prompt = 23; + string template_version_id = 24; } // Config represents execution configuration shared by all subsequent requests in the Session @@ -373,6 +374,11 @@ message Config { // state is the provisioner state (if any) bytes state = 2; string provisioner_log_level = 3; + // Template imports can omit template id + optional string template_id = 4; + // Dry runs omit version id + optional string template_version_id = 5; + optional bool exp_reuse_terraform_workspace = 6; // Whether to reuse existing terraform workspaces if they exist. } // ParseRequest consumes source-code to produce inputs. diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts index c5a7d16274a1c..ba9071ab625e8 100644 --- a/site/e2e/provisionerGenerated.ts +++ b/site/e2e/provisionerGenerated.ts @@ -405,6 +405,7 @@ export interface Metadata { runningAgentAuthTokens: RunningAgentAuthToken[]; taskId: string; taskPrompt: string; + templateVersionId: string; } /** Config represents execution configuration shared by all subsequent requests in the Session */ @@ -414,6 +415,16 @@ export interface Config { /** state is the provisioner state (if any) */ state: Uint8Array; provisionerLogLevel: string; + /** Template imports can omit template id */ + templateId?: + | string + | undefined; + /** Dry runs omit version id */ + templateVersionId?: + | string + | undefined; + /** Whether to reuse existing terraform workspaces if they exist. */ + expReuseTerraformWorkspace?: boolean | undefined; } /** ParseRequest consumes source-code to produce inputs. */ @@ -1298,6 +1309,9 @@ export const Metadata = { if (message.taskPrompt !== "") { writer.uint32(186).string(message.taskPrompt); } + if (message.templateVersionId !== "") { + writer.uint32(194).string(message.templateVersionId); + } return writer; }, }; @@ -1313,6 +1327,15 @@ export const Config = { if (message.provisionerLogLevel !== "") { writer.uint32(26).string(message.provisionerLogLevel); } + if (message.templateId !== undefined) { + writer.uint32(34).string(message.templateId); + } + if (message.templateVersionId !== undefined) { + writer.uint32(42).string(message.templateVersionId); + } + if (message.expReuseTerraformWorkspace !== undefined) { + writer.uint32(48).bool(message.expReuseTerraformWorkspace); + } return writer; }, }; From ac2c1616366e4124ecdf6afa712068e27cb57379 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Wed, 12 Nov 2025 11:11:51 -0800 Subject: [PATCH 133/255] fix(docs): add newlines to display GFM alerts correctly (#20747) Fixes this bug called out by @matifali on https://coder.com/docs/ai-coder/tasks: image [The docs for GFM alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) specify that the alert type (`[!NOTE]`, `[!IMPORTANT]`, etc) should be on its own line within the blockquote, with the alert text on the following line(s). --- docs/ai-coder/tasks-core-principles.md | 3 ++- docs/ai-coder/tasks-migration.md | 3 ++- docs/ai-coder/tasks.md | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/ai-coder/tasks-core-principles.md b/docs/ai-coder/tasks-core-principles.md index 66053d486d8de..fadd4273b0aed 100644 --- a/docs/ai-coder/tasks-core-principles.md +++ b/docs/ai-coder/tasks-core-principles.md @@ -56,7 +56,8 @@ Coder maintains various agentic modules; see [Coder Labs](https://registry.coder The following code snippet can be dropped into any existing template in Coder v2.28 or above to modify it into a Claude-Code enabled task template. This snippet also includes space for a setup script that will prime the agent for execution. -> [!NOTE] This requires at least version 2.13.0 of the `coder/coder` Terraform provider. +> [!NOTE] +> This requires at least version 2.13.0 of the `coder/coder` Terraform provider. ```hcl data "coder_parameter" "setup_script" { diff --git a/docs/ai-coder/tasks-migration.md b/docs/ai-coder/tasks-migration.md index d489c1d4742d7..6cd02ba2e7ba2 100644 --- a/docs/ai-coder/tasks-migration.md +++ b/docs/ai-coder/tasks-migration.md @@ -50,7 +50,8 @@ module "claude-code" { 1. Add the `coder_ai_task` resource and set `app_id` to the `task_app_id` output of the Claude module. -> [!NOTE] Refer to the documentation for the specific module you are using for the exact name of the output. +> [!NOTE] +> Refer to the documentation for the specific module you are using for the exact name of the output. ```diff resource "coder_ai_task" "task" { diff --git a/docs/ai-coder/tasks.md b/docs/ai-coder/tasks.md index c5235b208e8af..ae9bc8f4e62cd 100644 --- a/docs/ai-coder/tasks.md +++ b/docs/ai-coder/tasks.md @@ -45,7 +45,8 @@ To import the template and begin configuring it, import the example [Run Coder T A template becomes a Task-capable template if it defines a `coder_ai_task` resource. Coder analyzes template files during template version import to determine if these requirements are met. Try adding this terraform block to an existing template where you'll add our Claude Code module. -> [!NOTE] The `coder_ai_task` resource is not defined within the [Claude Code Module](https://registry.coder.com/modules/coder/claude-code?tab=readme). You need to define it yourself. +> [!NOTE] +> The `coder_ai_task` resource is not defined within the [Claude Code Module](https://registry.coder.com/modules/coder/claude-code?tab=readme). You need to define it yourself. ```hcl terraform { @@ -121,7 +122,8 @@ Because Tasks run unpredictable AI agents, often for background tasks, we recomm Alternatively, follow our guide for [custom agents](./custom-agents.md). -> [!IMPORTANT] Upgrading from Coder v2.27 or earlier? See the [Tasks Migration Guide](./tasks-migration.md) for breaking changes in v2.28.0. +> [!IMPORTANT] +> Upgrading from Coder v2.27 or earlier? See the [Tasks Migration Guide](./tasks-migration.md) for breaking changes in v2.28.0. ## Customizing the Task UI From 04727c06e8ed5dbed69cb16305d24e0500f8bbdf Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 12 Nov 2025 14:26:15 -0600 Subject: [PATCH 134/255] chore: add experiment toggle for terraform workspace caching (#20559) Experiments passed to provisioners to determine behavior. This adds `--experiments` flag to provisioner daemons. Prior to this, provisioners had no method to turn on/off experiments. --- cli/server.go | 1 + coderd/apidoc/docs.go | 7 +++++-- coderd/apidoc/swagger.json | 7 +++++-- coderd/coderd.go | 1 + .../provisionerdserver/provisionerdserver.go | 3 +++ .../provisionerdserver_test.go | 4 +++- codersdk/deployment.go | 4 ++++ docs/reference/api/schemas.md | 21 ++++++++++--------- docs/reference/cli/provisioner_start.md | 10 +++++++++ enterprise/cli/provisionerdaemonstart.go | 11 ++++++++++ .../coder_provisioner_start_--help.golden | 5 +++++ .../coderd/coderdenttest/coderdenttest.go | 1 + enterprise/coderd/provisionerdaemons.go | 1 + provisionersdk/serve.go | 2 ++ site/src/api/typesGenerated.ts | 2 ++ 15 files changed, 65 insertions(+), 15 deletions(-) diff --git a/cli/server.go b/cli/server.go index b12f5e0189c47..010e96d2fc693 100644 --- a/cli/server.go +++ b/cli/server.go @@ -1476,6 +1476,7 @@ func newProvisionerDaemon( Listener: terraformServer, Logger: provisionerLogger, WorkDirectory: workDir, + Experiments: coderAPI.Experiments, }, CachePath: tfDir, Tracer: tracer, diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index a67b2cb7c70ad..67dc8279278fa 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -14322,7 +14322,8 @@ const docTemplate = `{ "web-push", "oauth2", "mcp-server-http", - "workspace-sharing" + "workspace-sharing", + "terraform-directory-reuse" ], "x-enum-comments": { "ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.", @@ -14330,6 +14331,7 @@ const docTemplate = `{ "ExperimentMCPServerHTTP": "Enables the MCP HTTP server functionality.", "ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.", "ExperimentOAuth2": "Enables OAuth2 provider functionality.", + "ExperimentTerraformWorkspace": "Enables reuse of existing terraform directory for builds", "ExperimentWebPush": "Enables web push notifications through the browser.", "ExperimentWorkspaceSharing": "Enables updating workspace ACLs for sharing with users and groups.", "ExperimentWorkspaceUsage": "Enables the new workspace usage tracking." @@ -14342,7 +14344,8 @@ const docTemplate = `{ "ExperimentWebPush", "ExperimentOAuth2", "ExperimentMCPServerHTTP", - "ExperimentWorkspaceSharing" + "ExperimentWorkspaceSharing", + "ExperimentTerraformWorkspace" ] }, "codersdk.ExternalAPIKeyScopes": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 4e1bd64ebe125..1577e55b8693e 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -12929,7 +12929,8 @@ "web-push", "oauth2", "mcp-server-http", - "workspace-sharing" + "workspace-sharing", + "terraform-directory-reuse" ], "x-enum-comments": { "ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.", @@ -12937,6 +12938,7 @@ "ExperimentMCPServerHTTP": "Enables the MCP HTTP server functionality.", "ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.", "ExperimentOAuth2": "Enables OAuth2 provider functionality.", + "ExperimentTerraformWorkspace": "Enables reuse of existing terraform directory for builds", "ExperimentWebPush": "Enables web push notifications through the browser.", "ExperimentWorkspaceSharing": "Enables updating workspace ACLs for sharing with users and groups.", "ExperimentWorkspaceUsage": "Enables the new workspace usage tracking." @@ -12949,7 +12951,8 @@ "ExperimentWebPush", "ExperimentOAuth2", "ExperimentMCPServerHTTP", - "ExperimentWorkspaceSharing" + "ExperimentWorkspaceSharing", + "ExperimentTerraformWorkspace" ] }, "codersdk.ExternalAPIKeyScopes": { diff --git a/coderd/coderd.go b/coderd/coderd.go index fd361c1e0e1de..a9683ace20004 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1999,6 +1999,7 @@ func (api *API) CreateInMemoryTaggedProvisionerDaemon(dialCtx context.Context, n api.NotificationsEnqueuer, &api.PrebuildsReconciler, api.ProvisionerdServerMetrics, + api.Experiments, ) if err != nil { return nil, err diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index dec8ab6d38424..f5ffadb29fc9e 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -121,6 +121,7 @@ type server struct { NotificationsEnqueuer notifications.Enqueuer PrebuildsOrchestrator *atomic.Pointer[prebuilds.ReconciliationOrchestrator] UsageInserter *atomic.Pointer[usage.Inserter] + Experiments codersdk.Experiments OIDCConfig promoauth.OAuth2Config @@ -182,6 +183,7 @@ func NewServer( enqueuer notifications.Enqueuer, prebuildsOrchestrator *atomic.Pointer[prebuilds.ReconciliationOrchestrator], metrics *Metrics, + experiments codersdk.Experiments, ) (proto.DRPCProvisionerDaemonServer, error) { // Fail-fast if pointers are nil if lifecycleCtx == nil { @@ -253,6 +255,7 @@ func NewServer( PrebuildsOrchestrator: prebuildsOrchestrator, UsageInserter: usageInserter, metrics: metrics, + Experiments: experiments, } if s.heartbeatFn == nil { diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index d4597f6546978..3d31251fcdb5d 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -26,6 +26,7 @@ import ( "storj.io/drpc" "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/v2/coderd" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/quartz" "github.com/coder/serpent" @@ -4162,7 +4163,7 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi defOrg, err := db.GetDefaultOrganization(context.Background()) require.NoError(t, err, "default org not found") - deploymentValues := &codersdk.DeploymentValues{} + deploymentValues := coderdtest.DeploymentValues(t) var externalAuthConfigs []*externalauth.Config tss := testTemplateScheduleStore() uqhss := testUserQuietHoursScheduleStore() @@ -4285,6 +4286,7 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi notifEnq, &op, provisionerdserver.NewMetrics(logger), + coderd.ReadExperiments(logger, deploymentValues.Experiments), ) require.NoError(t, err) return srv, db, ps, daemon diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 4d79058b6835e..073ab7faede3e 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -3646,6 +3646,8 @@ const ( ExperimentOAuth2 Experiment = "oauth2" // Enables OAuth2 provider functionality. ExperimentMCPServerHTTP Experiment = "mcp-server-http" // Enables the MCP HTTP server functionality. ExperimentWorkspaceSharing Experiment = "workspace-sharing" // Enables updating workspace ACLs for sharing with users and groups. + // ExperimentTerraformWorkspace uses the "Terraform Workspaces" feature, not to be confused with Coder Workspaces. + ExperimentTerraformWorkspace Experiment = "terraform-directory-reuse" // Enables reuse of existing terraform directory for builds ) func (e Experiment) DisplayName() string { @@ -3666,6 +3668,8 @@ func (e Experiment) DisplayName() string { return "MCP HTTP Server Functionality" case ExperimentWorkspaceSharing: return "Workspace Sharing" + case ExperimentTerraformWorkspace: + return "Terraform Directory Reuse" default: // Split on hyphen and convert to title case // e.g. "web-push" -> "Web Push", "mcp-server-http" -> "Mcp Server Http" diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index f5ca96c98b045..e466abaa6fba0 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -4057,16 +4057,17 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o #### Enumerated Values -| Value | -|------------------------| -| `example` | -| `auto-fill-parameters` | -| `notifications` | -| `workspace-usage` | -| `web-push` | -| `oauth2` | -| `mcp-server-http` | -| `workspace-sharing` | +| Value | +|-----------------------------| +| `example` | +| `auto-fill-parameters` | +| `notifications` | +| `workspace-usage` | +| `web-push` | +| `oauth2` | +| `mcp-server-http` | +| `workspace-sharing` | +| `terraform-directory-reuse` | ## codersdk.ExternalAPIKeyScopes diff --git a/docs/reference/cli/provisioner_start.md b/docs/reference/cli/provisioner_start.md index 2a3c88ff93139..f278bac310cad 100644 --- a/docs/reference/cli/provisioner_start.md +++ b/docs/reference/cli/provisioner_start.md @@ -144,6 +144,16 @@ Serve prometheus metrics on the address defined by prometheus address. The bind address to serve prometheus metrics. +### --experiments + +| | | +|-------------|---------------------------------| +| Type | string-array | +| Environment | $CODER_EXPERIMENTS | +| YAML | experiments | + +Enable one or more experiments. These are not ready for production. Separate multiple experiments with commas, or enter '*' to opt-in to all available experiments. + ### -O, --org | | | diff --git a/enterprise/cli/provisionerdaemonstart.go b/enterprise/cli/provisionerdaemonstart.go index 1869007a85173..4c0ed003a18df 100644 --- a/enterprise/cli/provisionerdaemonstart.go +++ b/enterprise/cli/provisionerdaemonstart.go @@ -23,6 +23,7 @@ import ( "github.com/coder/coder/v2/cli/clilog" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/cli/cliutil" + "github.com/coder/coder/v2/coderd" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/drpcsdk" @@ -48,6 +49,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command { preSharedKey string provisionerKey string verbose bool + experiments []string prometheusEnable bool prometheusAddress string @@ -186,6 +188,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command { Listener: terraformServer, Logger: logger.Named("terraform"), WorkDirectory: tempDir, + Experiments: coderd.ReadExperiments(logger, experiments), }, CachePath: cacheDir, }) @@ -378,6 +381,14 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command { Value: serpent.StringOf(&prometheusAddress), Default: "127.0.0.1:2112", }, + { + Name: "Experiments", + Description: "Enable one or more experiments. These are not ready for production. Separate multiple experiments with commas, or enter '*' to opt-in to all available experiments.", + Flag: "experiments", + Env: "CODER_EXPERIMENTS", + Value: serpent.StringArrayOf(&experiments), + YAML: "experiments", + }, } orgContext.AttachOptions(cmd) diff --git a/enterprise/cli/testdata/coder_provisioner_start_--help.golden b/enterprise/cli/testdata/coder_provisioner_start_--help.golden index 439a2d68ba038..e3d4c69a8c45c 100644 --- a/enterprise/cli/testdata/coder_provisioner_start_--help.golden +++ b/enterprise/cli/testdata/coder_provisioner_start_--help.golden @@ -6,6 +6,11 @@ USAGE: Run a provisioner daemon OPTIONS: + --experiments string-array, $CODER_EXPERIMENTS + Enable one or more experiments. These are not ready for production. + Separate multiple experiments with commas, or enter '*' to opt-in to + all available experiments. + -O, --org string, $CODER_ORGANIZATION Select which organization (uuid or name) to use. diff --git a/enterprise/coderd/coderdenttest/coderdenttest.go b/enterprise/coderd/coderdenttest/coderdenttest.go index a31d1d495bb6e..29758c3dbf02a 100644 --- a/enterprise/coderd/coderdenttest/coderdenttest.go +++ b/enterprise/coderd/coderdenttest/coderdenttest.go @@ -414,6 +414,7 @@ func newExternalProvisionerDaemon(t testing.TB, client *codersdk.Client, org uui ServeOptions: &provisionersdk.ServeOptions{ Listener: provisionerSrv, WorkDirectory: t.TempDir(), + Experiments: codersdk.Experiments{}, }, })) }() diff --git a/enterprise/coderd/provisionerdaemons.go b/enterprise/coderd/provisionerdaemons.go index be03af29293f9..0f6db2508af97 100644 --- a/enterprise/coderd/provisionerdaemons.go +++ b/enterprise/coderd/provisionerdaemons.go @@ -362,6 +362,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) api.NotificationsEnqueuer, &api.AGPL.PrebuildsReconciler, api.ProvisionerdServerMetrics, + api.AGPL.Experiments, ) if err != nil { if !xerrors.Is(err, context.Canceled) { diff --git a/provisionersdk/serve.go b/provisionersdk/serve.go index c652cfa94949d..3bac226e58379 100644 --- a/provisionersdk/serve.go +++ b/provisionersdk/serve.go @@ -15,6 +15,7 @@ import ( "storj.io/drpc/drpcserver" "cdr.dev/slog" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/drpcsdk" "github.com/coder/coder/v2/coderd/tracing" @@ -30,6 +31,7 @@ type ServeOptions struct { Logger slog.Logger WorkDirectory string ExternalProvisioner bool + Experiments codersdk.Experiments } type Server interface { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 76a83e00c5545..ea65957316e53 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1895,6 +1895,7 @@ export type Experiment = | "mcp-server-http" | "notifications" | "oauth2" + | "terraform-directory-reuse" | "web-push" | "workspace-sharing" | "workspace-usage"; @@ -1905,6 +1906,7 @@ export const Experiments: Experiment[] = [ "mcp-server-http", "notifications", "oauth2", + "terraform-directory-reuse", "web-push", "workspace-sharing", "workspace-usage", From a8f2a8a44d93c928ec82bb72dec09a5e4351e704 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Thu, 13 Nov 2025 09:48:28 +1100 Subject: [PATCH 135/255] fix(cli): skip dry-run for workspace start/restart commands (#20754) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The `prepWorkspaceBuild()` function in `cli/create.go` was unconditionally executing dry-runs for **all** workspace actions. This caused unnecessary delays and "Planning workspace..." messages during `coder start` and `coder restart` commands when they should only happen during `coder create` and `coder update`. ## Root Cause The `prepWorkspaceBuild()` function is shared code called by: - **create command** - passes `WorkspaceCreate` action ✅ dry-run IS desired - **update command** - passes `WorkspaceUpdate` action ✅ dry-run IS desired - **start command** - passes `WorkspaceStart` action (or `WorkspaceUpdate` as fallback) ❌ dry-run NOT desired for `WorkspaceStart` - **restart command** - passes `WorkspaceRestart` action ❌ dry-run NOT desired - **scaletest commands** - pass `WorkspaceCreate` action ✅ dry-run IS desired ## Solution Wrapped the dry-run section (lines 580-627) in a conditional that only executes when `args.Action == WorkspaceCreate || args.Action == WorkspaceUpdate`. This skips dry-run for `WorkspaceStart` and `WorkspaceRestart` actions while preserving it for creation and explicit updates. ## Changes - Added conditional check around the entire dry-run logic block - Added clarifying comment explaining the intent - Changed from unconditional execution to: `if args.Action == WorkspaceCreate || args.Action == WorkspaceUpdate { ... }` ## Impact | Command | Action Type | Dry-run Before | Dry-run After | Status | |---------|-------------|----------------|---------------|--------| | `coder create` | `WorkspaceCreate` | ✅ Yes | ✅ Yes | Unchanged | | `coder update` | `WorkspaceUpdate` | ✅ Yes | ✅ Yes | Unchanged | | `coder start` (normal) | `WorkspaceStart` | ❌ Yes (bug) | ✅ No | **Fixed** | | `coder start` (template changed) | `WorkspaceUpdate` | ✅ Yes | ✅ Yes | Unchanged (correct behavior) | | `coder restart` | `WorkspaceRestart` | ❌ Yes (bug) | ✅ No | **Fixed** | | scaletest | `WorkspaceCreate` | ✅ Yes | ✅ Yes | Unchanged | ## Testing ✅ **Code compiles successfully** ```bash go build -o /dev/null ./cli/... ``` ✅ **All relevant tests pass locally** ```bash cd cli && go test -run "TestCreate|TestStart|TestRestart|TestUpdate" -v PASS ok github.com/coder/coder/v2/cli 3.337s ``` ✅ **All CI checks pass** - test-go-pg (ubuntu, macos, windows) ✅ - test-go-pg-17 ✅ - test-go-race-pg ✅ - test-e2e ✅ - All other checks ✅ ## Behavior Changes **Before:** - Users running `coder start` would see "Planning workspace..." and wait for unnecessary dry-run completion - Users running `coder restart` would experience unnecessary dry-run overhead **After:** - `coder start` (simple start) skips dry-run entirely (faster, more intuitive) - `coder start` (with template update) still shows dry-run (correct - user needs to see what's changing) - `coder restart` skips dry-run entirely (faster, more intuitive) - `coder create` maintains existing dry-run behavior (shows "Planning workspace..." and resource preview) - `coder update` maintains existing dry-run behavior (shows "Planning workspace..." and resource preview) ## Verification Manual testing should verify: 1. `coder create` still shows "Planning workspace..." ✅ 2. `coder update` still shows "Planning workspace..." ✅ 3. `coder start` (simple start) does NOT show "Planning workspace..." ✅ 4. `coder restart` does NOT show "Planning workspace..." ✅ --- cli/create.go | 92 +++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/cli/create.go b/cli/create.go index 05fe0824b5be1..225d05950e77c 100644 --- a/cli/create.go +++ b/cli/create.go @@ -577,53 +577,57 @@ func prepWorkspaceBuild(inv *serpent.Invocation, client *codersdk.Client, args p return nil, xerrors.Errorf("template version git auth: %w", err) } - // Run a dry-run with the given parameters to check correctness - dryRun, err := client.CreateTemplateVersionDryRun(inv.Context(), templateVersion.ID, codersdk.CreateTemplateVersionDryRunRequest{ - WorkspaceName: args.NewWorkspaceName, - RichParameterValues: buildParameters, - }) - if err != nil { - return nil, xerrors.Errorf("begin workspace dry-run: %w", err) - } + // Only perform dry-run for workspace creation and updates + // Skip for start and restart to avoid unnecessary delays + if args.Action == WorkspaceCreate || args.Action == WorkspaceUpdate { + // Run a dry-run with the given parameters to check correctness + dryRun, err := client.CreateTemplateVersionDryRun(inv.Context(), templateVersion.ID, codersdk.CreateTemplateVersionDryRunRequest{ + WorkspaceName: args.NewWorkspaceName, + RichParameterValues: buildParameters, + }) + if err != nil { + return nil, xerrors.Errorf("begin workspace dry-run: %w", err) + } - matchedProvisioners, err := client.TemplateVersionDryRunMatchedProvisioners(inv.Context(), templateVersion.ID, dryRun.ID) - if err != nil { - return nil, xerrors.Errorf("get matched provisioners: %w", err) - } - cliutil.WarnMatchedProvisioners(inv.Stdout, &matchedProvisioners, dryRun) - _, _ = fmt.Fprintln(inv.Stdout, "Planning workspace...") - err = cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{ - Fetch: func() (codersdk.ProvisionerJob, error) { - return client.TemplateVersionDryRun(inv.Context(), templateVersion.ID, dryRun.ID) - }, - Cancel: func() error { - return client.CancelTemplateVersionDryRun(inv.Context(), templateVersion.ID, dryRun.ID) - }, - Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) { - return client.TemplateVersionDryRunLogsAfter(inv.Context(), templateVersion.ID, dryRun.ID, 0) - }, - // Don't show log output for the dry-run unless there's an error. - Silent: true, - }) - if err != nil { - // TODO (Dean): reprompt for parameter values if we deem it to - // be a validation error - return nil, xerrors.Errorf("dry-run workspace: %w", err) - } + matchedProvisioners, err := client.TemplateVersionDryRunMatchedProvisioners(inv.Context(), templateVersion.ID, dryRun.ID) + if err != nil { + return nil, xerrors.Errorf("get matched provisioners: %w", err) + } + cliutil.WarnMatchedProvisioners(inv.Stdout, &matchedProvisioners, dryRun) + _, _ = fmt.Fprintln(inv.Stdout, "Planning workspace...") + err = cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{ + Fetch: func() (codersdk.ProvisionerJob, error) { + return client.TemplateVersionDryRun(inv.Context(), templateVersion.ID, dryRun.ID) + }, + Cancel: func() error { + return client.CancelTemplateVersionDryRun(inv.Context(), templateVersion.ID, dryRun.ID) + }, + Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) { + return client.TemplateVersionDryRunLogsAfter(inv.Context(), templateVersion.ID, dryRun.ID, 0) + }, + // Don't show log output for the dry-run unless there's an error. + Silent: true, + }) + if err != nil { + // TODO (Dean): reprompt for parameter values if we deem it to + // be a validation error + return nil, xerrors.Errorf("dry-run workspace: %w", err) + } - resources, err := client.TemplateVersionDryRunResources(inv.Context(), templateVersion.ID, dryRun.ID) - if err != nil { - return nil, xerrors.Errorf("get workspace dry-run resources: %w", err) - } + resources, err := client.TemplateVersionDryRunResources(inv.Context(), templateVersion.ID, dryRun.ID) + if err != nil { + return nil, xerrors.Errorf("get workspace dry-run resources: %w", err) + } - err = cliui.WorkspaceResources(inv.Stdout, resources, cliui.WorkspaceResourcesOptions{ - WorkspaceName: args.NewWorkspaceName, - // Since agents haven't connected yet, hiding this makes more sense. - HideAgentState: true, - Title: "Workspace Preview", - }) - if err != nil { - return nil, xerrors.Errorf("get resources: %w", err) + err = cliui.WorkspaceResources(inv.Stdout, resources, cliui.WorkspaceResourcesOptions{ + WorkspaceName: args.NewWorkspaceName, + // Since agents haven't connected yet, hiding this makes more sense. + HideAgentState: true, + Title: "Workspace Preview", + }) + if err != nil { + return nil, xerrors.Errorf("get resources: %w", err) + } } return buildParameters, nil From f559e51d7f5dce823ac81da44153e03863d1dbcd Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Thu, 13 Nov 2025 12:29:03 +1100 Subject: [PATCH 136/255] feat: add `duration` to the Request Logs details (#20756) This pull-request simply implements a new field within our `Request Log`s details view where-in we describe back to the user how long their request took based on the `end_date - start_date` in a human-like way. Users can hover the value to see the `title=` for the true value. CleanShot 2025-11-13 at 11 51 56@2x --- .../RequestLogsRow/RequestLogsRow.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx index 06f821ef88cf7..4bdd2013a8f50 100644 --- a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx +++ b/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx @@ -15,6 +15,7 @@ import { } from "lucide-react"; import { type FC, Fragment, useState } from "react"; import { cn } from "utils/cn"; +import { humanDuration } from "utils/time"; type RequestLogsRowProps = { interception: AIBridgeInterception; @@ -34,6 +35,13 @@ export const RequestLogsRow: FC = ({ interception }) => { 0, ); const toolCalls = interception.tool_usages.length; + const duration = + interception.ended_at && + Math.max( + 0, + new Date(interception.ended_at).getTime() - + new Date(interception.started_at).getTime(), + ); return ( <> @@ -123,6 +131,15 @@ export const RequestLogsRow: FC = ({ interception }) => { )} + {(duration || duration === 0) && ( + <> +
Duration:
+
+ {humanDuration(duration)} +
+ + )} +
Initiator:
{interception.initiator.username} From 5bfbb0301f956a31e7fde53179a030153d3742e4 Mon Sep 17 00:00:00 2001 From: david-fraley <67079030+david-fraley@users.noreply.github.com> Date: Thu, 13 Nov 2025 06:22:42 -0600 Subject: [PATCH 137/255] chore: update release calendar for new patches (#20748) --- docs/install/kubernetes.md | 8 ++++---- docs/install/rancher.md | 4 ++-- docs/install/releases/index.md | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/install/kubernetes.md b/docs/install/kubernetes.md index f4543e3403e02..8c2a0ed4d1d23 100644 --- a/docs/install/kubernetes.md +++ b/docs/install/kubernetes.md @@ -136,7 +136,7 @@ We support two release channels: mainline and stable - read the helm install coder coder-v2/coder \ --namespace coder \ --values values.yaml \ - --version 2.28.0 + --version 2.28.3 ``` - **OCI Registry** @@ -147,7 +147,7 @@ We support two release channels: mainline and stable - read the helm install coder oci://ghcr.io/coder/chart/coder \ --namespace coder \ --values values.yaml \ - --version 2.28.0 + --version 2.28.3 ``` - **Stable** Coder release: @@ -160,7 +160,7 @@ We support two release channels: mainline and stable - read the helm install coder coder-v2/coder \ --namespace coder \ --values values.yaml \ - --version 2.27.3 + --version 2.27.6 ``` - **OCI Registry** @@ -171,7 +171,7 @@ We support two release channels: mainline and stable - read the helm install coder oci://ghcr.io/coder/chart/coder \ --namespace coder \ --values values.yaml \ - --version 2.27.3 + --version 2.27.6 ``` You can watch Coder start up by running `kubectl get pods -n coder`. Once Coder diff --git a/docs/install/rancher.md b/docs/install/rancher.md index e41f4cf42a85b..a28e408c2e4eb 100644 --- a/docs/install/rancher.md +++ b/docs/install/rancher.md @@ -134,8 +134,8 @@ kubectl create secret generic coder-db-url -n coder \ 1. Select a Coder version: - - **Mainline**: `2.28.0` - - **Stable**: `2.27.3` + - **Mainline**: `2.28.3` + - **Stable**: `2.27.6` Learn more about release channels in the [Releases documentation](./releases/index.md). diff --git a/docs/install/releases/index.md b/docs/install/releases/index.md index 6cb98bed9f45c..db870f55c4701 100644 --- a/docs/install/releases/index.md +++ b/docs/install/releases/index.md @@ -60,9 +60,9 @@ pages. | [2.23](https://coder.com/changelog/coder-2-23) | June 03, 2025 | Not Supported | [v2.23.5](https://github.com/coder/coder/releases/tag/v2.23.5) | | [2.24](https://coder.com/changelog/coder-2-24) | July 01, 2025 | Not Supported | [v2.24.4](https://github.com/coder/coder/releases/tag/v2.24.4) | | [2.25](https://coder.com/changelog/coder-2-25) | August 05, 2025 | Not Supported | [v2.25.3](https://github.com/coder/coder/releases/tag/v2.25.3) | -| [2.26](https://coder.com/changelog/coder-2-26) | September 03, 2025 | Security Support | [v2.26.3](https://github.com/coder/coder/releases/tag/v2.26.3) | -| [2.27](https://coder.com/changelog/coder-2-27) | October 02, 2025 | Stable | [v2.27.3](https://github.com/coder/coder/releases/tag/v2.27.3) | -| [2.28](https://coder.com/changelog/coder-2-28) | November 04, 2025 | Mainline | [v2.28.0](https://github.com/coder/coder/releases/tag/v2.28.0) | +| [2.26](https://coder.com/changelog/coder-2-26) | September 03, 2025 | Security Support | [v2.26.4](https://github.com/coder/coder/releases/tag/v2.26.4) | +| [2.27](https://coder.com/changelog/coder-2-27) | October 02, 2025 | Stable | [v2.27.6](https://github.com/coder/coder/releases/tag/v2.27.6) | +| [2.28](https://coder.com/changelog/coder-2-28) | November 04, 2025 | Mainline | [v2.28.3](https://github.com/coder/coder/releases/tag/v2.28.3) | | 2.29 | | Not Released | N/A | From a3c851c0e6c6dd3eb38c4b9bebe00141dca79a1b Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Thu, 13 Nov 2025 16:53:02 +0400 Subject: [PATCH 138/255] feat: add task status reporting load generator runner (#20538) Adds the Runner, Config, and Metrics for the scaletest load generator for task status. Part of https://github.com/coder/internal/issues/913 --- scaletest/taskstatus/client.go | 148 +++++++ scaletest/taskstatus/config.go | 73 ++++ scaletest/taskstatus/metrics.go | 36 ++ scaletest/taskstatus/run.go | 232 +++++++++++ scaletest/taskstatus/run_internal_test.go | 482 ++++++++++++++++++++++ testutil/logger.go | 30 ++ 6 files changed, 1001 insertions(+) create mode 100644 scaletest/taskstatus/client.go create mode 100644 scaletest/taskstatus/config.go create mode 100644 scaletest/taskstatus/metrics.go create mode 100644 scaletest/taskstatus/run.go create mode 100644 scaletest/taskstatus/run_internal_test.go diff --git a/scaletest/taskstatus/client.go b/scaletest/taskstatus/client.go new file mode 100644 index 0000000000000..f4271f4f31a33 --- /dev/null +++ b/scaletest/taskstatus/client.go @@ -0,0 +1,148 @@ +package taskstatus + +import ( + "context" + "net/http" + "net/url" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/agentsdk" +) + +// createExternalWorkspaceResult contains the results from creating an external workspace. +type createExternalWorkspaceResult struct { + WorkspaceID uuid.UUID + AgentToken string +} + +// client abstracts the details of using codersdk.Client for workspace operations. +// This interface allows for easier testing by enabling mock implementations and +// provides a cleaner separation of concerns. +// +// The interface is designed to be initialized in two phases: +// 1. Create the client with newClient(coderClient) +// 2. Configure logging when the io.Writer is available in Run() +type client interface { + // createExternalWorkspace creates an external workspace and returns the workspace ID + // and agent token for the first external agent found in the workspace resources. + createExternalWorkspace(ctx context.Context, req codersdk.CreateWorkspaceRequest) (createExternalWorkspaceResult, error) + + // watchWorkspace watches for updates to a workspace. + watchWorkspace(ctx context.Context, workspaceID uuid.UUID) (<-chan codersdk.Workspace, error) + + // initialize sets up the client with the provided logger, which is only available after Run() is called. + initialize(logger slog.Logger) +} + +// appStatusPatcher abstracts the details of using agentsdk.Client for updating app status. +// This interface is separate from client because it requires an agent token which is only +// available after creating an external workspace. +type appStatusPatcher interface { + // patchAppStatus updates the status of a workspace app. + patchAppStatus(ctx context.Context, req agentsdk.PatchAppStatus) error + + // initialize sets up the patcher with the provided logger and agent token. + initialize(logger slog.Logger, agentToken string) +} + +// sdkClient is the concrete implementation of the client interface using +// codersdk.Client. +type sdkClient struct { + coderClient *codersdk.Client +} + +// newClient creates a new client implementation using the provided codersdk.Client. +func newClient(coderClient *codersdk.Client) client { + return &sdkClient{ + coderClient: coderClient, + } +} + +func (c *sdkClient) createExternalWorkspace(ctx context.Context, req codersdk.CreateWorkspaceRequest) (createExternalWorkspaceResult, error) { + // Create the workspace + workspace, err := c.coderClient.CreateUserWorkspace(ctx, codersdk.Me, req) + if err != nil { + return createExternalWorkspaceResult{}, err + } + + // Get the workspace with latest build details + workspace, err = c.coderClient.WorkspaceByOwnerAndName(ctx, codersdk.Me, workspace.Name, codersdk.WorkspaceOptions{}) + if err != nil { + return createExternalWorkspaceResult{}, err + } + + // Find external agents in resources + for _, resource := range workspace.LatestBuild.Resources { + if resource.Type != "coder_external_agent" || len(resource.Agents) == 0 { + continue + } + + // Get credentials for the first agent + agent := resource.Agents[0] + credentials, err := c.coderClient.WorkspaceExternalAgentCredentials(ctx, workspace.ID, agent.Name) + if err != nil { + return createExternalWorkspaceResult{}, err + } + + return createExternalWorkspaceResult{ + WorkspaceID: workspace.ID, + AgentToken: credentials.AgentToken, + }, nil + } + + return createExternalWorkspaceResult{}, xerrors.Errorf("no external agent found in workspace") +} + +func (c *sdkClient) watchWorkspace(ctx context.Context, workspaceID uuid.UUID) (<-chan codersdk.Workspace, error) { + return c.coderClient.WatchWorkspace(ctx, workspaceID) +} + +func (c *sdkClient) initialize(logger slog.Logger) { + // Configure the coder client logging + c.coderClient.SetLogger(logger) + c.coderClient.SetLogBodies(true) +} + +// sdkAppStatusPatcher is the concrete implementation of the appStatusPatcher interface +// using agentsdk.Client. +type sdkAppStatusPatcher struct { + agentClient *agentsdk.Client + url *url.URL + httpClient *http.Client +} + +// newAppStatusPatcher creates a new appStatusPatcher implementation. +func newAppStatusPatcher(client *codersdk.Client) appStatusPatcher { + return &sdkAppStatusPatcher{ + url: client.URL, + httpClient: client.HTTPClient, + } +} + +func (p *sdkAppStatusPatcher) patchAppStatus(ctx context.Context, req agentsdk.PatchAppStatus) error { + if p.agentClient == nil { + panic("agentClient not initialized - call initialize first") + } + return p.agentClient.PatchAppStatus(ctx, req) +} + +func (p *sdkAppStatusPatcher) initialize(logger slog.Logger, agentToken string) { + // Create and configure the agent client with the provided token + p.agentClient = agentsdk.New( + p.url, + agentsdk.WithFixedToken(agentToken), + codersdk.WithHTTPClient(p.httpClient), + codersdk.WithLogger(logger), + codersdk.WithLogBodies(), + ) +} + +// Ensure sdkClient implements the client interface. +var _ client = (*sdkClient)(nil) + +// Ensure sdkAppStatusPatcher implements the appStatusPatcher interface. +var _ appStatusPatcher = (*sdkAppStatusPatcher)(nil) diff --git a/scaletest/taskstatus/config.go b/scaletest/taskstatus/config.go new file mode 100644 index 0000000000000..1c3f26cfabfa1 --- /dev/null +++ b/scaletest/taskstatus/config.go @@ -0,0 +1,73 @@ +package taskstatus + +import ( + "sync" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" +) + +type Config struct { + // TemplateID is the template ID to use for creating the external workspace. + TemplateID uuid.UUID `json:"template_id"` + + // WorkspaceName is the name for the external workspace to create. + WorkspaceName string `json:"workspace_name"` + + // AppSlug is the slug of the app designated as the AI Agent. + AppSlug string `json:"app_slug"` + + // When the runner has connected to the watch-ws endpoint, it will call Done once on this wait group. Used to + // coordinate multiple runners from the higher layer. + ConnectedWaitGroup *sync.WaitGroup `json:"-"` + + // We read on this channel before starting to report task statuses. Used to coordinate multiple runners from the + // higher layer. + StartReporting chan struct{} `json:"-"` + + // Time between reporting task statuses. + ReportStatusPeriod time.Duration `json:"report_status_period"` + + // Total time to report task statuses, starting from when we successfully read from the StartReporting channel. + ReportStatusDuration time.Duration `json:"report_status_duration"` + + Metrics *Metrics `json:"-"` + MetricLabelValues []string `json:"metric_label_values"` +} + +func (c *Config) Validate() error { + if c.TemplateID == uuid.Nil { + return xerrors.Errorf("validate template_id: must not be nil") + } + + if c.WorkspaceName == "" { + return xerrors.Errorf("validate workspace_name: must not be empty") + } + + if c.AppSlug == "" { + return xerrors.Errorf("validate app_slug: must not be empty") + } + + if c.ConnectedWaitGroup == nil { + return xerrors.Errorf("validate connected_wait_group: must not be nil") + } + + if c.StartReporting == nil { + return xerrors.Errorf("validate start_reporting: must not be nil") + } + + if c.ReportStatusPeriod <= 0 { + return xerrors.Errorf("validate report_status_period: must be greater than zero") + } + + if c.ReportStatusDuration <= 0 { + return xerrors.Errorf("validate report_status_duration: must be greater than zero") + } + + if c.Metrics == nil { + return xerrors.Errorf("validate metrics: must not be nil") + } + + return nil +} diff --git a/scaletest/taskstatus/metrics.go b/scaletest/taskstatus/metrics.go new file mode 100644 index 0000000000000..1b312a41a3338 --- /dev/null +++ b/scaletest/taskstatus/metrics.go @@ -0,0 +1,36 @@ +package taskstatus + +import "github.com/prometheus/client_golang/prometheus" + +type Metrics struct { + TaskStatusToWorkspaceUpdateLatencySeconds prometheus.HistogramVec + MissingStatusUpdatesTotal prometheus.CounterVec + ReportTaskStatusErrorsTotal prometheus.CounterVec +} + +func NewMetrics(reg prometheus.Registerer, labelNames ...string) *Metrics { + m := &Metrics{ + TaskStatusToWorkspaceUpdateLatencySeconds: *prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "task_status_to_workspace_update_latency_seconds", + Help: "Time in seconds between reporting a task status and receiving the workspace update.", + }, labelNames), + MissingStatusUpdatesTotal: *prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "missing_status_updates_total", + Help: "Total number of missing status updates.", + }, labelNames), + ReportTaskStatusErrorsTotal: *prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "report_task_status_errors_total", + Help: "Total number of errors when reporting task status.", + }, labelNames), + } + reg.MustRegister(m.TaskStatusToWorkspaceUpdateLatencySeconds) + reg.MustRegister(m.MissingStatusUpdatesTotal) + reg.MustRegister(m.ReportTaskStatusErrorsTotal) + return m +} diff --git a/scaletest/taskstatus/run.go b/scaletest/taskstatus/run.go new file mode 100644 index 0000000000000..59b11237d6c9e --- /dev/null +++ b/scaletest/taskstatus/run.go @@ -0,0 +1,232 @@ +package taskstatus + +import ( + "context" + "io" + "strconv" + "strings" + "sync" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" + + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/scaletest/harness" + "github.com/coder/coder/v2/scaletest/loadtestutil" + "github.com/coder/quartz" +) + +const statusUpdatePrefix = "scaletest status update:" + +type Runner struct { + client client + patcher appStatusPatcher + cfg Config + + logger slog.Logger + + // workspaceID is set after creating the external workspace + workspaceID uuid.UUID + + mu sync.Mutex + reportTimes map[int]time.Time + doneReporting bool + + // testing only + clock quartz.Clock +} + +var _ harness.Runnable = &Runner{} + +// NewRunner creates a new Runner with the provided codersdk.Client and configuration. +func NewRunner(coderClient *codersdk.Client, cfg Config) *Runner { + return &Runner{ + client: newClient(coderClient), + patcher: newAppStatusPatcher(coderClient), + cfg: cfg, + clock: quartz.NewReal(), + reportTimes: make(map[int]time.Time), + } +} + +func (r *Runner) Run(ctx context.Context, name string, logs io.Writer) error { + shouldMarkConnectedDone := true + defer func() { + if shouldMarkConnectedDone { + r.cfg.ConnectedWaitGroup.Done() + } + }() + + logs = loadtestutil.NewSyncWriter(logs) + r.logger = slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug).Named(name) + r.client.initialize(r.logger) + + // Create the external workspace + r.logger.Info(ctx, "creating external workspace", + slog.F("template_id", r.cfg.TemplateID), + slog.F("workspace_name", r.cfg.WorkspaceName)) + + result, err := r.client.createExternalWorkspace(ctx, codersdk.CreateWorkspaceRequest{ + TemplateID: r.cfg.TemplateID, + Name: r.cfg.WorkspaceName, + }) + if err != nil { + return xerrors.Errorf("create external workspace: %w", err) + } + + // Set the workspace ID + r.workspaceID = result.WorkspaceID + r.logger.Info(ctx, "created external workspace", slog.F("workspace_id", r.workspaceID)) + + // Initialize the patcher with the agent token + r.patcher.initialize(r.logger, result.AgentToken) + r.logger.Info(ctx, "initialized app status patcher with agent token") + + // ensure these labels are initialized, so we see the time series right away in prometheus. + r.cfg.Metrics.MissingStatusUpdatesTotal.WithLabelValues(r.cfg.MetricLabelValues...).Add(0) + r.cfg.Metrics.ReportTaskStatusErrorsTotal.WithLabelValues(r.cfg.MetricLabelValues...).Add(0) + + workspaceUpdatesCtx, cancelWorkspaceUpdates := context.WithCancel(ctx) + defer cancelWorkspaceUpdates() + workspaceUpdatesResult := make(chan error, 1) + shouldMarkConnectedDone = false // we are passing this responsibility to the watchWorkspaceUpdates goroutine + go func() { + workspaceUpdatesResult <- r.watchWorkspaceUpdates(workspaceUpdatesCtx) + }() + + err = r.reportTaskStatus(ctx) + if err != nil { + return xerrors.Errorf("report task status: %w", err) + } + + err = <-workspaceUpdatesResult + if err != nil { + return xerrors.Errorf("watch workspace: %w", err) + } + return nil +} + +func (r *Runner) watchWorkspaceUpdates(ctx context.Context) error { + shouldMarkConnectedDone := true + defer func() { + if shouldMarkConnectedDone { + r.cfg.ConnectedWaitGroup.Done() + } + }() + updates, err := r.client.watchWorkspace(ctx, r.workspaceID) + if err != nil { + return xerrors.Errorf("watch workspace: %w", err) + } + shouldMarkConnectedDone = false + r.cfg.ConnectedWaitGroup.Done() + defer func() { + r.mu.Lock() + defer r.mu.Unlock() + r.cfg.Metrics.MissingStatusUpdatesTotal. + WithLabelValues(r.cfg.MetricLabelValues...). + Add(float64(len(r.reportTimes))) + }() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case workspace := <-updates: + if workspace.LatestAppStatus == nil { + continue + } + msgNo, ok := parseStatusMessage(workspace.LatestAppStatus.Message) + if !ok { + continue + } + + r.mu.Lock() + reportTime, ok := r.reportTimes[msgNo] + delete(r.reportTimes, msgNo) + allDone := r.doneReporting && len(r.reportTimes) == 0 + r.mu.Unlock() + + if !ok { + return xerrors.Errorf("report time not found for message %d", msgNo) + } + latency := r.clock.Since(reportTime, "watchWorkspaceUpdates") + r.cfg.Metrics.TaskStatusToWorkspaceUpdateLatencySeconds. + WithLabelValues(r.cfg.MetricLabelValues...). + Observe(latency.Seconds()) + if allDone { + return nil + } + } + } +} + +func (r *Runner) reportTaskStatus(ctx context.Context) error { + defer func() { + r.mu.Lock() + defer r.mu.Unlock() + r.doneReporting = true + }() + + select { + case <-ctx.Done(): + return ctx.Err() + case <-r.cfg.StartReporting: + r.logger.Info(ctx, "starting to report task status") + } + startedReporting := r.clock.Now("reportTaskStatus", "startedReporting") + msgNo := 0 + + done := xerrors.New("done reporting task status") // sentinel error + waiter := r.clock.TickerFunc(ctx, r.cfg.ReportStatusPeriod, func() error { + r.mu.Lock() + now := r.clock.Now("reportTaskStatus", "tick") + r.reportTimes[msgNo] = now + // It's important that we set doneReporting along with a final report, since the watchWorkspaceUpdates goroutine + // needs a update to wake up and check if we're done. We could introduce a secondary signaling channel, but + // it adds a lot of complexity and will be hard to test. We expect the tick period to be much smaller than the + // report status duration, so one extra tick is not a big deal. + if now.After(startedReporting.Add(r.cfg.ReportStatusDuration)) { + r.doneReporting = true + } + r.mu.Unlock() + + err := r.patcher.patchAppStatus(ctx, agentsdk.PatchAppStatus{ + AppSlug: r.cfg.AppSlug, + Message: statusUpdatePrefix + strconv.Itoa(msgNo), + State: codersdk.WorkspaceAppStatusStateWorking, + URI: "https://example.com/example-status/", + }) + if err != nil { + r.logger.Error(ctx, "failed to report task status", slog.Error(err)) + r.cfg.Metrics.ReportTaskStatusErrorsTotal.WithLabelValues(r.cfg.MetricLabelValues...).Inc() + } + msgNo++ + // note that it's safe to read r.doneReporting here without a lock because we're the only goroutine that sets + // it. + if r.doneReporting { + return done // causes the ticker to exit due to the sentinel error + } + return nil + }, "reportTaskStatus") + err := waiter.Wait() + if xerrors.Is(err, done) { + return nil + } + return err +} + +func parseStatusMessage(message string) (int, bool) { + if !strings.HasPrefix(message, statusUpdatePrefix) { + return 0, false + } + message = strings.TrimPrefix(message, statusUpdatePrefix) + msgNo, err := strconv.Atoi(message) + if err != nil { + return 0, false + } + return msgNo, true +} diff --git a/scaletest/taskstatus/run_internal_test.go b/scaletest/taskstatus/run_internal_test.go new file mode 100644 index 0000000000000..3821b794aeb2c --- /dev/null +++ b/scaletest/taskstatus/run_internal_test.go @@ -0,0 +1,482 @@ +package taskstatus + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/google/uuid" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "github.com/coder/quartz" + + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/testutil" +) + +// fakeClient implements the client interface for testing +type fakeClient struct { + t *testing.T + logger slog.Logger + + // Channels for controlling the behavior + workspaceUpdatesCh chan codersdk.Workspace +} + +func newFakeClient(t *testing.T) *fakeClient { + return &fakeClient{ + t: t, + workspaceUpdatesCh: make(chan codersdk.Workspace), + } +} + +func (m *fakeClient) initialize(logger slog.Logger) { + m.logger = logger +} + +func (m *fakeClient) watchWorkspace(ctx context.Context, workspaceID uuid.UUID) (<-chan codersdk.Workspace, error) { + m.logger.Debug(ctx, "called fake WatchWorkspace", slog.F("workspace_id", workspaceID.String())) + return m.workspaceUpdatesCh, nil +} + +const testAgentToken = "test-agent-token" + +func (m *fakeClient) createExternalWorkspace(ctx context.Context, req codersdk.CreateWorkspaceRequest) (createExternalWorkspaceResult, error) { + m.logger.Debug(ctx, "called fake CreateExternalWorkspace", slog.F("req", req)) + // Return a fake workspace ID and token for testing + return createExternalWorkspaceResult{ + WorkspaceID: uuid.UUID{1, 2, 3, 4}, // Fake workspace ID + AgentToken: testAgentToken, + }, nil +} + +// fakeAppStatusPatcher implements the appStatusPatcher interface for testing +type fakeAppStatusPatcher struct { + t *testing.T + logger slog.Logger + agentToken string + + // Channels for controlling the behavior + patchStatusCalls chan agentsdk.PatchAppStatus + patchStatusErrors chan error +} + +func newFakeAppStatusPatcher(t *testing.T) *fakeAppStatusPatcher { + return &fakeAppStatusPatcher{ + t: t, + patchStatusCalls: make(chan agentsdk.PatchAppStatus), + patchStatusErrors: make(chan error, 1), + } +} + +func (p *fakeAppStatusPatcher) initialize(logger slog.Logger, agentToken string) { + p.logger = logger + p.agentToken = agentToken +} + +func (p *fakeAppStatusPatcher) patchAppStatus(ctx context.Context, req agentsdk.PatchAppStatus) error { + assert.NotEmpty(p.t, p.agentToken) + p.logger.Debug(ctx, "called fake PatchAppStatus", slog.F("req", req)) + // Send the request to the channel so tests can verify it + select { + case p.patchStatusCalls <- req: + case <-ctx.Done(): + return ctx.Err() + } + + // Check if there's an error to return + select { + case err := <-p.patchStatusErrors: + return err + default: + return nil + } +} + +func TestRunner_Run(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitShort) + + mClock := quartz.NewMock(t) + fClient := newFakeClient(t) + fPatcher := newFakeAppStatusPatcher(t) + templateID := uuid.UUID{5, 6, 7, 8} + workspaceName := "test-workspace" + appSlug := "test-app" + + reg := prometheus.NewRegistry() + metrics := NewMetrics(reg, "test") + + connectedWaitGroup := &sync.WaitGroup{} + connectedWaitGroup.Add(1) + startReporting := make(chan struct{}) + + cfg := Config{ + TemplateID: templateID, + WorkspaceName: workspaceName, + AppSlug: appSlug, + ConnectedWaitGroup: connectedWaitGroup, + StartReporting: startReporting, + ReportStatusPeriod: 10 * time.Second, + ReportStatusDuration: 35 * time.Second, + Metrics: metrics, + MetricLabelValues: []string{"test"}, + } + runner := &Runner{ + client: fClient, + patcher: fPatcher, + cfg: cfg, + clock: mClock, + reportTimes: make(map[int]time.Time), + } + + tickerTrap := mClock.Trap().TickerFunc("reportTaskStatus") + defer tickerTrap.Close() + sinceTrap := mClock.Trap().Since("watchWorkspaceUpdates") + defer sinceTrap.Close() + + // Run the runner in a goroutine + runErr := make(chan error, 1) + go func() { + runErr <- runner.Run(ctx, "test-runner", testutil.NewTestLogWriter(t)) + }() + + // Wait for the runner to connect and watch workspace + connectedWaitGroup.Wait() + + // Signal to start reporting + close(startReporting) + + // Wait for the initial TickerFunc call before advancing time, otherwise our ticks will be off. + tickerTrap.MustWait(ctx).MustRelease(ctx) + + // at this point, the patcher must be initialized + require.Equal(t, testAgentToken, fPatcher.agentToken) + + updateDelay := time.Duration(0) + for i := 0; i < 4; i++ { + tickWaiter := mClock.Advance((10 * time.Second) - updateDelay) + + patchCall := testutil.RequireReceive(ctx, t, fPatcher.patchStatusCalls) + require.Equal(t, appSlug, patchCall.AppSlug) + require.Equal(t, fmt.Sprintf("scaletest status update:%d", i), patchCall.Message) + require.Equal(t, codersdk.WorkspaceAppStatusStateWorking, patchCall.State) + tickWaiter.MustWait(ctx) + + // Send workspace update 1, 2, 3, or 4 seconds after the report + updateDelay = time.Duration(i+1) * time.Second + mClock.Advance(updateDelay) + + workspace := codersdk.Workspace{ + LatestAppStatus: &codersdk.WorkspaceAppStatus{ + Message: fmt.Sprintf("scaletest status update:%d", i), + }, + } + testutil.RequireSend(ctx, t, fClient.workspaceUpdatesCh, workspace) + sinceTrap.MustWait(ctx).MustRelease(ctx) + } + + // Wait for the runner to complete + err := testutil.RequireReceive(ctx, t, runErr) + require.NoError(t, err) + + // Verify metrics were updated correctly + metricFamilies, err := reg.Gather() + require.NoError(t, err) + + var latencyMetricFound bool + var missingUpdatesFound bool + for _, mf := range metricFamilies { + switch mf.GetName() { + case "coderd_scaletest_task_status_to_workspace_update_latency_seconds": + latencyMetricFound = true + require.Len(t, mf.GetMetric(), 1) + hist := mf.GetMetric()[0].GetHistogram() + assert.Equal(t, uint64(4), hist.GetSampleCount()) + case "coderd_scaletest_missing_status_updates_total": + missingUpdatesFound = true + require.Len(t, mf.GetMetric(), 1) + counter := mf.GetMetric()[0].GetCounter() + assert.Equal(t, float64(0), counter.GetValue()) + } + } + assert.True(t, latencyMetricFound, "latency metric not found") + assert.True(t, missingUpdatesFound, "missing updates metric not found") +} + +func TestRunner_RunMissedUpdate(t *testing.T) { + t.Parallel() + + testCtx := testutil.Context(t, testutil.WaitShort) + runCtx, cancel := context.WithCancel(testCtx) + defer cancel() + + mClock := quartz.NewMock(t) + fClient := newFakeClient(t) + fPatcher := newFakeAppStatusPatcher(t) + templateID := uuid.UUID{5, 6, 7, 8} + workspaceName := "test-workspace" + appSlug := "test-app" + + reg := prometheus.NewRegistry() + metrics := NewMetrics(reg, "test") + + connectedWaitGroup := &sync.WaitGroup{} + connectedWaitGroup.Add(1) + startReporting := make(chan struct{}) + + cfg := Config{ + TemplateID: templateID, + WorkspaceName: workspaceName, + AppSlug: appSlug, + ConnectedWaitGroup: connectedWaitGroup, + StartReporting: startReporting, + ReportStatusPeriod: 10 * time.Second, + ReportStatusDuration: 35 * time.Second, + Metrics: metrics, + MetricLabelValues: []string{"test"}, + } + runner := &Runner{ + client: fClient, + patcher: fPatcher, + cfg: cfg, + clock: mClock, + reportTimes: make(map[int]time.Time), + } + + tickerTrap := mClock.Trap().TickerFunc("reportTaskStatus") + defer tickerTrap.Close() + sinceTrap := mClock.Trap().Since("watchWorkspaceUpdates") + defer sinceTrap.Close() + + // Run the runner in a goroutine + runErr := make(chan error, 1) + go func() { + runErr <- runner.Run(runCtx, "test-runner", testutil.NewTestLogWriter(t)) + }() + + // Wait for the runner to connect and watch workspace + connectedWaitGroup.Wait() + + // Signal to start reporting + close(startReporting) + + // Wait for the initial TickerFunc call before advancing time, otherwise our ticks will be off. + tickerTrap.MustWait(testCtx).MustRelease(testCtx) + + updateDelay := time.Duration(0) + for i := 0; i < 4; i++ { + tickWaiter := mClock.Advance((10 * time.Second) - updateDelay) + patchCall := testutil.RequireReceive(testCtx, t, fPatcher.patchStatusCalls) + require.Equal(t, appSlug, patchCall.AppSlug) + require.Equal(t, fmt.Sprintf("scaletest status update:%d", i), patchCall.Message) + require.Equal(t, codersdk.WorkspaceAppStatusStateWorking, patchCall.State) + tickWaiter.MustWait(testCtx) + + // Send workspace update 1, 2, 3, or 4 seconds after the report + updateDelay = time.Duration(i+1) * time.Second + mClock.Advance(updateDelay) + + workspace := codersdk.Workspace{ + LatestAppStatus: &codersdk.WorkspaceAppStatus{ + Message: fmt.Sprintf("scaletest status update:%d", i), + }, + } + if i != 2 { + // skip the third update, to test that we report missed updates and still complete. + testutil.RequireSend(testCtx, t, fClient.workspaceUpdatesCh, workspace) + sinceTrap.MustWait(testCtx).MustRelease(testCtx) + } + } + + // Cancel the run context to simulate the runner being killed. + cancel() + + // Wait for the runner to complete + err := testutil.RequireReceive(testCtx, t, runErr) + require.ErrorIs(t, err, context.Canceled) + + // Verify metrics were updated correctly + metricFamilies, err := reg.Gather() + require.NoError(t, err) + + // Check that metrics were recorded + var latencyMetricFound bool + var missingUpdatesFound bool + for _, mf := range metricFamilies { + switch mf.GetName() { + case "coderd_scaletest_task_status_to_workspace_update_latency_seconds": + latencyMetricFound = true + require.Len(t, mf.GetMetric(), 1) + hist := mf.GetMetric()[0].GetHistogram() + assert.Equal(t, uint64(3), hist.GetSampleCount()) + case "coderd_scaletest_missing_status_updates_total": + missingUpdatesFound = true + require.Len(t, mf.GetMetric(), 1) + counter := mf.GetMetric()[0].GetCounter() + assert.Equal(t, float64(1), counter.GetValue()) + } + } + assert.True(t, latencyMetricFound, "latency metric not found") + assert.True(t, missingUpdatesFound, "missing updates metric not found") +} + +func TestRunner_Run_WithErrors(t *testing.T) { + t.Parallel() + + testCtx := testutil.Context(t, testutil.WaitShort) + runCtx, cancel := context.WithCancel(testCtx) + defer cancel() + + mClock := quartz.NewMock(t) + fClient := newFakeClient(t) + fPatcher := newFakeAppStatusPatcher(t) + templateID := uuid.UUID{5, 6, 7, 8} + workspaceName := "test-workspace" + appSlug := "test-app" + + reg := prometheus.NewRegistry() + metrics := NewMetrics(reg, "test") + + connectedWaitGroup := &sync.WaitGroup{} + connectedWaitGroup.Add(1) + startReporting := make(chan struct{}) + + cfg := Config{ + TemplateID: templateID, + WorkspaceName: workspaceName, + AppSlug: appSlug, + ConnectedWaitGroup: connectedWaitGroup, + StartReporting: startReporting, + ReportStatusPeriod: 10 * time.Second, + ReportStatusDuration: 35 * time.Second, + Metrics: metrics, + MetricLabelValues: []string{"test"}, + } + runner := &Runner{ + client: fClient, + patcher: fPatcher, + cfg: cfg, + clock: mClock, + reportTimes: make(map[int]time.Time), + } + + tickerTrap := mClock.Trap().TickerFunc("reportTaskStatus") + defer tickerTrap.Close() + + // Run the runner in a goroutine + runErr := make(chan error, 1) + go func() { + runErr <- runner.Run(runCtx, "test-runner", testutil.NewTestLogWriter(t)) + }() + + connectedWaitGroup.Wait() + close(startReporting) + + // Wait for the initial TickerFunc call before advancing time, otherwise our ticks will be off. + tickerTrap.MustWait(testCtx).MustRelease(testCtx) + + for i := 0; i < 4; i++ { + tickWaiter := mClock.Advance(10 * time.Second) + testutil.RequireSend(testCtx, t, fPatcher.patchStatusErrors, xerrors.New("a bad thing happened")) + _ = testutil.RequireReceive(testCtx, t, fPatcher.patchStatusCalls) + tickWaiter.MustWait(testCtx) + } + + // Cancel the run context to simulate the runner being killed. + cancel() + + // Wait for the runner to complete + err := testutil.RequireReceive(testCtx, t, runErr) + require.ErrorIs(t, err, context.Canceled) + + // Verify metrics were updated correctly + metricFamilies, err := reg.Gather() + require.NoError(t, err) + + var missingUpdatesFound bool + var reportTaskStatusErrorsFound bool + for _, mf := range metricFamilies { + switch mf.GetName() { + case "coderd_scaletest_missing_status_updates_total": + missingUpdatesFound = true + require.Len(t, mf.GetMetric(), 1) + counter := mf.GetMetric()[0].GetCounter() + assert.Equal(t, float64(4), counter.GetValue()) + case "coderd_scaletest_report_task_status_errors_total": + reportTaskStatusErrorsFound = true + require.Len(t, mf.GetMetric(), 1) + counter := mf.GetMetric()[0].GetCounter() + assert.Equal(t, float64(4), counter.GetValue()) + } + } + + assert.True(t, missingUpdatesFound, "missing updates metric not found") + assert.True(t, reportTaskStatusErrorsFound, "report task status errors metric not found") +} + +func TestParseStatusMessage(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + message string + wantNum int + wantOk bool + }{ + { + name: "valid message", + message: "scaletest status update:42", + wantNum: 42, + wantOk: true, + }, + { + name: "valid message zero", + message: "scaletest status update:0", + wantNum: 0, + wantOk: true, + }, + { + name: "invalid prefix", + message: "wrong prefix:42", + wantNum: 0, + wantOk: false, + }, + { + name: "invalid number", + message: "scaletest status update:abc", + wantNum: 0, + wantOk: false, + }, + { + name: "empty message", + message: "", + wantNum: 0, + wantOk: false, + }, + { + name: "missing number", + message: "scaletest status update:", + wantNum: 0, + wantOk: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + gotNum, gotOk := parseStatusMessage(tt.message) + assert.Equal(t, tt.wantNum, gotNum) + assert.Equal(t, tt.wantOk, gotOk) + }) + } +} diff --git a/testutil/logger.go b/testutil/logger.go index 88b6e20bada51..051d7c9daf1ab 100644 --- a/testutil/logger.go +++ b/testutil/logger.go @@ -2,7 +2,9 @@ package testutil import ( "context" + "io" "strings" + "sync" "testing" "github.com/hashicorp/yamux" @@ -51,3 +53,31 @@ func isQueryCanceledError(err error) bool { } return false } + +type testLogWriter struct { + t testing.TB + mu sync.Mutex + testOver bool +} + +func NewTestLogWriter(t testing.TB) io.Writer { + w := &testLogWriter{t: t} + t.Cleanup(func() { + w.mu.Lock() + defer w.mu.Unlock() + w.testOver = true + }) + return w +} + +func (w *testLogWriter) Write(p []byte) (n int, err error) { + n = len(p) + w.mu.Lock() + defer w.mu.Unlock() + if w.testOver { + return n, nil + } + w.t.Logf("%q", string(p)) + + return n, nil +} From 14f08444a90ac9a2c68dc3b5e8d5dbba18b3f2b6 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 13 Nov 2025 13:00:12 +0000 Subject: [PATCH 139/255] fix: send all params instead of only touched params (#20740) resolves #20257 Ensure all parameters are sent in the web socket request instead of only touched parameters. Using touched parameters is irrelevant for the workspace parameters page in workspace settings because parameters that appear here have already been chosen by the user during workspace creation. So all parameters should be sent in the web socket request whether have been touched in the form or not. --- ...orkspaceParametersPageViewExperimental.tsx | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx index 52228f19d9f40..54bf0907da752 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx @@ -48,18 +48,6 @@ export const WorkspaceParametersPageViewExperimental: FC< onCancel, templateVersionId, }) => { - const autofillByName = Object.fromEntries( - autofillParameters.map((param) => [param.name, param]), - ); - const initialTouched = parameters.reduce( - (touched, parameter) => { - if (autofillByName[parameter.name] !== undefined) { - touched[parameter.name] = true; - } - return touched; - }, - {} as Record, - ); const form = useFormik({ onSubmit, initialValues: { @@ -68,7 +56,6 @@ export const WorkspaceParametersPageViewExperimental: FC< autofillParameters, ), }, - initialTouched, validationSchema: useValidationSchemaForDynamicParameters(parameters), enableReinitialize: false, validateOnChange: true, @@ -89,28 +76,23 @@ export const WorkspaceParametersPageViewExperimental: FC< name: parameter.name, value, }); - form.setFieldTouched(parameter.name, true); sendDynamicParamsRequest(parameter, value); }; - // Send the changed parameter and all touched parameters to the websocket const sendDynamicParamsRequest = ( parameter: PreviewParameter, value: string, ) => { const formInputs: Record = {}; - formInputs[parameter.name] = value; const parameters = form.values.rich_parameter_values ?? []; - - for (const [fieldName, isTouched] of Object.entries(form.touched)) { - if (isTouched && fieldName !== parameter.name) { - const param = parameters.find((p) => p.name === fieldName); - if (param?.value) { - formInputs[fieldName] = param.value; - } + for (const param of parameters) { + if (param?.name && param?.value) { + formInputs[param.name] = param.value; } } + formInputs[parameter.name] = value; + sendMessage(formInputs); }; From 9ca5b44b56238392a481b55243a269006257d9f4 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 13 Nov 2025 07:50:17 -0600 Subject: [PATCH 140/255] chore: implement persistent terraform directories (experimental) (#20563) Prior to this, every workspace build ran `terraform init` in a fresh directory. This would mean the `modules` are downloaded fresh. If the module is not pinned, subsequent workspace builds would have different modules. --- .../provisionerdserver/provisionerdserver.go | 20 +- .../provisionerdserver_test.go | 1 + provisioner/terraform/executor.go | 8 +- provisioner/terraform/modules.go | 2 +- provisioner/terraform/serve.go | 2 +- provisionerd/provisionerd_test.go | 5 +- provisionersdk/cleanup.go | 48 --- provisionersdk/cleanup_test.go | 14 +- provisionersdk/session.go | 27 +- provisionersdk/tfpath/tfpath.go | 62 +++- provisionersdk/tfpath/x/tfpath.go | 320 ++++++++++++++++++ 11 files changed, 428 insertions(+), 81 deletions(-) delete mode 100644 provisionersdk/cleanup.go create mode 100644 provisionersdk/tfpath/x/tfpath.go diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index f5ffadb29fc9e..ce58d90468026 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -699,16 +699,19 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo } } + activeVersion := template.ActiveVersionID == templateVersion.ID protoJob.Type = &proto.AcquiredJob_WorkspaceBuild_{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ - WorkspaceBuildId: workspaceBuild.ID.String(), - WorkspaceName: workspace.Name, - State: workspaceBuild.ProvisionerState, - RichParameterValues: convertRichParameterValues(workspaceBuildParameters), - PreviousParameterValues: convertRichParameterValues(lastWorkspaceBuildParameters), - VariableValues: asVariableValues(templateVariables), - ExternalAuthProviders: externalAuthProviders, - ExpReuseTerraformWorkspace: ptr.Ref(false), // TODO: Toggle based on experiment + WorkspaceBuildId: workspaceBuild.ID.String(), + WorkspaceName: workspace.Name, + State: workspaceBuild.ProvisionerState, + RichParameterValues: convertRichParameterValues(workspaceBuildParameters), + PreviousParameterValues: convertRichParameterValues(lastWorkspaceBuildParameters), + VariableValues: asVariableValues(templateVariables), + ExternalAuthProviders: externalAuthProviders, + // If active and experiment is enabled, allow workspace reuse existing TF + // workspaces (directories) for a faster startup. + ExpReuseTerraformWorkspace: ptr.Ref(activeVersion && s.Experiments.Enabled(codersdk.ExperimentTerraformWorkspace)), Metadata: &sdkproto.Metadata{ CoderUrl: s.AccessURL.String(), WorkspaceTransition: transition, @@ -722,6 +725,7 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo WorkspaceOwnerId: owner.ID.String(), TemplateId: template.ID.String(), TemplateName: template.Name, + TemplateVersionId: templateVersion.ID.String(), TemplateVersion: templateVersion.Name, WorkspaceOwnerSessionToken: sessionToken, WorkspaceOwnerSshPublicKey: ownerSSHPublicKey, diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 3d31251fcdb5d..4dc8621736b5c 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -452,6 +452,7 @@ func TestAcquireJob(t *testing.T) { TemplateId: template.ID.String(), TemplateName: template.Name, TemplateVersion: version.Name, + TemplateVersionId: version.ID.String(), WorkspaceOwnerSessionToken: sessionToken, WorkspaceOwnerSshPublicKey: sshKey.PublicKey, WorkspaceOwnerSshPrivateKey: sshKey.PrivateKey, diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index 345b0e72fb90c..2a4fbb3d1808d 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -41,7 +41,7 @@ type executor struct { // cachePath and files must not be used by multiple processes at once. cachePath string cliConfigPath string - files tfpath.Layout + files tfpath.Layouter // used to capture execution times at various stages timings *timingAggregator } @@ -536,7 +536,11 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) { if err != nil { return "", err } - args := []string{"graph"} + args := []string{ + "graph", + // TODO: When the plan is present, we should probably use it? + // "-plan=" + e.files.PlanFilePath(), + } if ver.GreaterThanOrEqual(version170) { args = append(args, "-type=plan") } diff --git a/provisioner/terraform/modules.go b/provisioner/terraform/modules.go index 38bfd65e84d6c..048a5b3314a2c 100644 --- a/provisioner/terraform/modules.go +++ b/provisioner/terraform/modules.go @@ -58,7 +58,7 @@ func parseModulesFile(filePath string) ([]*proto.Module, error) { // getModules returns the modules from the modules file if it exists. // It returns nil if the file does not exist. // Modules become available after terraform init. -func getModules(files tfpath.Layout) ([]*proto.Module, error) { +func getModules(files tfpath.Layouter) ([]*proto.Module, error) { filePath := files.ModulesFilePath() if _, err := os.Stat(filePath); os.IsNotExist(err) { return nil, nil diff --git a/provisioner/terraform/serve.go b/provisioner/terraform/serve.go index 60951a8da136b..6b14282f9f472 100644 --- a/provisioner/terraform/serve.go +++ b/provisioner/terraform/serve.go @@ -161,7 +161,7 @@ func (s *server) startTrace(ctx context.Context, name string, opts ...trace.Span ))...) } -func (s *server) executor(files tfpath.Layout, stage database.ProvisionerJobTimingStage) *executor { +func (s *server) executor(files tfpath.Layouter, stage database.ProvisionerJobTimingStage) *executor { return &executor{ server: s, mut: s.execMut, diff --git a/provisionerd/provisionerd_test.go b/provisionerd/provisionerd_test.go index f9977d0e8eb1a..fc4d069a88597 100644 --- a/provisionerd/provisionerd_test.go +++ b/provisionerd/provisionerd_test.go @@ -26,6 +26,7 @@ import ( "github.com/coder/coder/v2/provisionerd/proto" "github.com/coder/coder/v2/provisionersdk" sdkproto "github.com/coder/coder/v2/provisionersdk/proto" + "github.com/coder/coder/v2/provisionersdk/tfpath" "github.com/coder/coder/v2/testutil" ) @@ -318,8 +319,8 @@ func TestProvisionerd(t *testing.T) { JobId: "test", Provisioner: "someprovisioner", TemplateSourceArchive: testutil.CreateTar(t, map[string]string{ - "test.txt": "content", - provisionersdk.ReadmeFile: "# A cool template 😎\n", + "test.txt": "content", + tfpath.ReadmeFile: "# A cool template 😎\n", }), Type: &proto.AcquiredJob_TemplateImport_{ TemplateImport: &proto.AcquiredJob_TemplateImport{ diff --git a/provisionersdk/cleanup.go b/provisionersdk/cleanup.go deleted file mode 100644 index b515c636b4eba..0000000000000 --- a/provisionersdk/cleanup.go +++ /dev/null @@ -1,48 +0,0 @@ -package provisionersdk - -import ( - "context" - "path/filepath" - "time" - - "github.com/spf13/afero" - "golang.org/x/xerrors" - - "cdr.dev/slog" -) - -// CleanStaleSessions browses the work directory searching for stale session -// directories. Coder provisioner is supposed to remove them once after finishing the provisioning, -// but there is a risk of keeping them in case of a failure. -func CleanStaleSessions(ctx context.Context, workDirectory string, fs afero.Fs, now time.Time, logger slog.Logger) error { - entries, err := afero.ReadDir(fs, workDirectory) - if err != nil { - return xerrors.Errorf("can't read %q directory", workDirectory) - } - - for _, fi := range entries { - dirName := fi.Name() - - if fi.IsDir() && isValidSessionDir(dirName) { - sessionDirPath := filepath.Join(workDirectory, dirName) - - modTime := fi.ModTime() // fallback to modTime if modTime is not available (afero) - - if modTime.Add(staleSessionRetention).After(now) { - continue - } - - logger.Info(ctx, "remove stale session directory", slog.F("session_path", sessionDirPath)) - err = fs.RemoveAll(sessionDirPath) - if err != nil { - return xerrors.Errorf("can't remove %q directory: %w", sessionDirPath, err) - } - } - } - return nil -} - -func isValidSessionDir(dirName string) bool { - match, err := filepath.Match(sessionDirPrefix+"*", dirName) - return err == nil && match -} diff --git a/provisionersdk/cleanup_test.go b/provisionersdk/cleanup_test.go index d60ef55a7c6d4..3bc0064f88132 100644 --- a/provisionersdk/cleanup_test.go +++ b/provisionersdk/cleanup_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "cdr.dev/slog" - "github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk/tfpath" "github.com/coder/coder/v2/testutil" ) @@ -47,9 +46,12 @@ func TestStaleSessions(t *testing.T) { addSessionFolder(t, fs, second, now.Add(-8*24*time.Hour)) third := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, third, now.Add(-9*24*time.Hour)) + // tfDir is a fake session that will clean up the others + tfDir := tfpath.Session(workDirectory, uuid.NewString()) // when - provisionersdk.CleanStaleSessions(ctx, workDirectory, fs, now, logger) + err := tfDir.CleanStaleSessions(ctx, logger, fs, now) + require.NoError(t, err) // then entries, err := afero.ReadDir(fs, workDirectory) @@ -70,9 +72,11 @@ func TestStaleSessions(t *testing.T) { addSessionFolder(t, fs, first, now.Add(-7*24*time.Hour)) second := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, second, now.Add(-6*24*time.Hour)) + tfDir := tfpath.Session(workDirectory, uuid.NewString()) // when - provisionersdk.CleanStaleSessions(ctx, workDirectory, fs, now, logger) + err := tfDir.CleanStaleSessions(ctx, logger, fs, now) + require.NoError(t, err) // then entries, err := afero.ReadDir(fs, workDirectory) @@ -94,9 +98,11 @@ func TestStaleSessions(t *testing.T) { addSessionFolder(t, fs, first, now.Add(-6*24*time.Hour)) second := tfpath.Session(workDirectory, uuid.NewString()) addSessionFolder(t, fs, second, now.Add(-5*24*time.Hour)) + tfDir := tfpath.Session(workDirectory, uuid.NewString()) // when - provisionersdk.CleanStaleSessions(ctx, workDirectory, fs, now, logger) + err := tfDir.CleanStaleSessions(ctx, logger, fs, now) + require.NoError(t, err) // then entries, err := afero.ReadDir(fs, workDirectory) diff --git a/provisionersdk/session.go b/provisionersdk/session.go index 68a72190071d9..59034a761e09d 100644 --- a/provisionersdk/session.go +++ b/provisionersdk/session.go @@ -13,22 +13,16 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/drpcsdk" "github.com/coder/coder/v2/provisionersdk/tfpath" + "github.com/coder/coder/v2/provisionersdk/tfpath/x" protobuf "google.golang.org/protobuf/proto" "github.com/coder/coder/v2/provisionersdk/proto" ) -const ( - // ReadmeFile is the location we look for to extract documentation from template versions. - ReadmeFile = "README.md" - - sessionDirPrefix = "Session" - staleSessionRetention = 7 * 24 * time.Hour -) - // protoServer is a wrapper that translates the dRPC protocol into a Session with method calls into the Server. type protoServer struct { server Server @@ -43,11 +37,6 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error server: p.server, } - err := CleanStaleSessions(s.Context(), p.opts.WorkDirectory, afero.NewOsFs(), time.Now(), s.Logger) - if err != nil { - return xerrors.Errorf("unable to clean stale sessions %q: %w", s.Files, err) - } - s.Files = tfpath.Session(p.opts.WorkDirectory, sessID) defer func() { @@ -67,6 +56,16 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error s.logLevel = proto.LogLevel_value[strings.ToUpper(s.Config.ProvisionerLogLevel)] } + if p.opts.Experiments.Enabled(codersdk.ExperimentTerraformWorkspace) { + s.Files = x.SessionDir(p.opts.WorkDirectory, sessID, config) + } + + // Cleanup any previously left stale sessions. + err = s.Files.CleanStaleSessions(s.Context(), s.Logger, afero.NewOsFs(), time.Now()) + if err != nil { + return xerrors.Errorf("unable to clean stale sessions %q: %w", s.Files, err) + } + err = s.Files.ExtractArchive(s.Context(), s.Logger, afero.NewOsFs(), s.Config) if err != nil { return xerrors.Errorf("extract archive: %w", err) @@ -199,7 +198,7 @@ func (s *Session) handleRequests() error { type Session struct { Logger slog.Logger - Files tfpath.Layout + Files tfpath.Layouter Config *proto.Config server Server diff --git a/provisionersdk/tfpath/tfpath.go b/provisionersdk/tfpath/tfpath.go index 8662a5f096950..019552e48d0de 100644 --- a/provisionersdk/tfpath/tfpath.go +++ b/provisionersdk/tfpath/tfpath.go @@ -19,11 +19,28 @@ import ( "github.com/coder/coder/v2/provisionersdk/proto" ) +type Layouter interface { + WorkDirectory() string + StateFilePath() string + PlanFilePath() string + TerraformLockFile() string + ReadmeFilePath() string + TerraformMetadataDir() string + ModulesDirectory() string + ModulesFilePath() string + ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error + Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) + CleanStaleSessions(ctx context.Context, logger slog.Logger, fs afero.Fs, now time.Time) error +} + +var _ Layouter = (*Layout)(nil) + const ( // ReadmeFile is the location we look for to extract documentation from template versions. ReadmeFile = "README.md" - sessionDirPrefix = "Session" + sessionDirPrefix = "Session" + staleSessionRetention = 7 * 24 * time.Hour ) // Session creates a directory structure layout for terraform execution. The @@ -34,6 +51,10 @@ func Session(parentDirPath, sessionID string) Layout { return Layout(filepath.Join(parentDirPath, sessionDirPrefix+sessionID)) } +func FromWorkingDirectory(workDir string) Layout { + return Layout(workDir) +} + // Layout is the terraform execution working directory structure. // It also contains some methods for common file operations within that layout. // Such as "Cleanup" and "ExtractArchive". @@ -82,6 +103,8 @@ func (l Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero return xerrors.Errorf("create work directory %q: %w", l.WorkDirectory(), err) } + // TODO: Pass in cfg.TemplateSourceArchive, not the full config. + // niling out the config field is a bit hacky. reader := tar.NewReader(bytes.NewBuffer(cfg.TemplateSourceArchive)) // for safety, nil out the reference on Config, since the reader now owns it. cfg.TemplateSourceArchive = nil @@ -190,3 +213,40 @@ func (l Layout) Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) { logger.Error(ctx, "failed to clean up work directory after multiple attempts", slog.F("path", path), slog.Error(err)) } + +// CleanStaleSessions browses the work directory searching for stale session +// directories. Coder provisioner is supposed to remove them once after finishing the provisioning, +// but there is a risk of keeping them in case of a failure. +func (l Layout) CleanStaleSessions(ctx context.Context, logger slog.Logger, fs afero.Fs, now time.Time) error { + parent := filepath.Dir(l.WorkDirectory()) + entries, err := afero.ReadDir(fs, filepath.Dir(l.WorkDirectory())) + if err != nil { + return xerrors.Errorf("can't read %q directory", parent) + } + + for _, fi := range entries { + dirName := fi.Name() + + if fi.IsDir() && isValidSessionDir(dirName) { + sessionDirPath := filepath.Join(parent, dirName) + + modTime := fi.ModTime() // fallback to modTime if modTime is not available (afero) + + if modTime.Add(staleSessionRetention).After(now) { + continue + } + + logger.Info(ctx, "remove stale session directory", slog.F("session_path", sessionDirPath)) + err = fs.RemoveAll(sessionDirPath) + if err != nil { + return xerrors.Errorf("can't remove %q directory: %w", sessionDirPath, err) + } + } + } + return nil +} + +func isValidSessionDir(dirName string) bool { + match, err := filepath.Match(sessionDirPrefix+"*", dirName) + return err == nil && match +} diff --git a/provisionersdk/tfpath/x/tfpath.go b/provisionersdk/tfpath/x/tfpath.go new file mode 100644 index 0000000000000..bc91315324158 --- /dev/null +++ b/provisionersdk/tfpath/x/tfpath.go @@ -0,0 +1,320 @@ +package x + +// This file will replace the `tfpath.go` in the parent `tfpath` package when the +// `terraform-workspace` experiment is graduated. + +import ( + "archive/tar" + "bytes" + "context" + "fmt" + "hash/crc32" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/google/uuid" + "github.com/spf13/afero" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "github.com/coder/coder/v2/provisionersdk/proto" + "github.com/coder/coder/v2/provisionersdk/tfpath" +) + +var _ tfpath.Layouter = (*Layout)(nil) + +func SessionDir(parentDir, sessID string, config *proto.Config) Layout { + // TODO: These conditionals are messy. nil, "", or uuid.Nil are all considered the same. Maybe a helper function? + missingID := config.TemplateId == nil || *config.TemplateId == "" || *config.TemplateId == uuid.Nil.String() || + config.TemplateVersionId == nil || *config.TemplateVersionId == "" || *config.TemplateVersionId == uuid.Nil.String() + + // Both templateID and templateVersionID must be set to reuse workspace. + if config.ExpReuseTerraformWorkspace == nil || !*config.ExpReuseTerraformWorkspace || missingID { + return EphemeralSessionDir(parentDir, sessID) + } + + return Layout{ + workDirectory: filepath.Join(parentDir, *config.TemplateId, *config.TemplateVersionId), + sessionID: sessID, + ephemeral: false, + } +} + +// EphemeralSessionDir returns the directory name with mandatory prefix. These +// directories are created for each provisioning session and are meant to be +// ephemeral. +func EphemeralSessionDir(parentDir, sessID string) Layout { + return Layout{ + workDirectory: filepath.Join(parentDir, sessionDirPrefix+sessID), + sessionID: sessID, + ephemeral: true, + } +} + +type Layout struct { + workDirectory string + sessionID string + ephemeral bool +} + +const ( + // ReadmeFile is the location we look for to extract documentation from template versions. + ReadmeFile = "README.md" + + sessionDirPrefix = "Session" +) + +func (td Layout) WorkDirectory() string { + return td.workDirectory +} + +// StateSessionDirectory follows the same directory structure as Terraform +// workspaces. All build specific state is stored within this directory. +// +// These files should be cleaned up on exit. In the case of a failure, they will +// not collide with other builds since each build uses a unique session ID. +func (td Layout) StateSessionDirectory() string { + return filepath.Join(td.workDirectory, "terraform.tfstate.d", td.sessionID) +} + +func (td Layout) StateFilePath() string { + return filepath.Join(td.StateSessionDirectory(), "terraform.tfstate") +} + +func (td Layout) PlanFilePath() string { + return filepath.Join(td.StateSessionDirectory(), "terraform.tfplan") +} + +func (td Layout) TerraformLockFile() string { + return filepath.Join(td.WorkDirectory(), ".terraform.lock.hcl") +} + +func (td Layout) ReadmeFilePath() string { + return filepath.Join(td.WorkDirectory(), ReadmeFile) +} + +func (td Layout) TerraformMetadataDir() string { + return filepath.Join(td.WorkDirectory(), ".terraform") +} + +func (td Layout) ModulesDirectory() string { + return filepath.Join(td.TerraformMetadataDir(), "modules") +} + +func (td Layout) ModulesFilePath() string { + return filepath.Join(td.ModulesDirectory(), "modules.json") +} + +func (td Layout) WorkspaceEnvironmentFilePath() string { + return filepath.Join(td.TerraformMetadataDir(), "environment") +} + +func (td Layout) Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) { + var err error + path := td.WorkDirectory() + if !td.ephemeral { + // Non-ephemeral directories only clean up the session subdirectory. + // Leaving in place the wider work directory for reuse. + path = td.StateSessionDirectory() + } + for attempt := 0; attempt < 5; attempt++ { + err := fs.RemoveAll(path) + if err != nil { + // On Windows, open files cannot be removed. + // When the provisioner daemon is shutting down, + // it may take a few milliseconds for processes to exit. + // See: https://github.com/golang/go/issues/50510 + logger.Debug(ctx, "failed to clean work directory; trying again", slog.Error(err)) + // TODO: Should we abort earlier if the context is done? + time.Sleep(250 * time.Millisecond) + continue + } + logger.Debug(ctx, "cleaned up work directory", slog.F("path", path)) + return + } + + logger.Error(ctx, "failed to clean up work directory after multiple attempts", + slog.F("path", path), slog.Error(err)) +} + +func (td Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error { + logger.Info(ctx, "unpacking template source archive", + slog.F("size_bytes", len(cfg.TemplateSourceArchive)), + ) + + err := fs.MkdirAll(td.WorkDirectory(), 0o700) + if err != nil { + return xerrors.Errorf("create work directory %q: %w", td.WorkDirectory(), err) + } + + err = fs.MkdirAll(td.StateSessionDirectory(), 0o700) + if err != nil { + return xerrors.Errorf("create state directory %q: %w", td.WorkDirectory(), err) + } + + // TODO: This is a bit hacky. We should use `terraform workspace select` to create this + // environment file. However, since we know the backend is `local`, this is a quicker + // way to accomplish the same thing. + err = td.SelectWorkspace(fs) + if err != nil { + return xerrors.Errorf("select terraform workspace: %w", err) + } + + reader := tar.NewReader(bytes.NewBuffer(cfg.TemplateSourceArchive)) + // for safety, nil out the reference on Config, since the reader now owns it. + cfg.TemplateSourceArchive = nil + for { + header, err := reader.Next() + if err != nil { + if xerrors.Is(err, io.EOF) { + break + } + return xerrors.Errorf("read template source archive: %w", err) + } + logger.Debug(context.Background(), "read archive entry", + slog.F("name", header.Name), + slog.F("mod_time", header.ModTime), + slog.F("size", header.Size)) + + // Security: don't untar absolute or relative paths, as this can allow a malicious tar to overwrite + // files outside the workdir. + if !filepath.IsLocal(header.Name) { + return xerrors.Errorf("refusing to extract to non-local path") + } + // nolint: gosec + headerPath := filepath.Join(td.WorkDirectory(), header.Name) + if !strings.HasPrefix(headerPath, filepath.Clean(td.WorkDirectory())) { + return xerrors.New("tar attempts to target relative upper directory") + } + mode := header.FileInfo().Mode() + if mode == 0 { + mode = 0o600 + } + + // Always check for context cancellation before reading the next header. + // This is mainly important for unit tests, since a canceled context means + // the underlying directory is going to be deleted. There still exists + // the small race condition that the context is canceled after this, and + // before the disk write. + if ctx.Err() != nil { + return xerrors.Errorf("context canceled: %w", ctx.Err()) + } + switch header.Typeflag { + case tar.TypeDir: + err = fs.MkdirAll(headerPath, mode) + if err != nil { + return xerrors.Errorf("mkdir %q: %w", headerPath, err) + } + logger.Debug(context.Background(), "extracted directory", + slog.F("path", headerPath), + slog.F("mode", fmt.Sprintf("%O", mode))) + case tar.TypeReg: + // TODO: If we are overwriting an existing file, that means we are reusing + // the terraform directory. In that case, we should check the file content + // matches what already exists on disk. Or just continue to overwrite it. + file, err := fs.OpenFile(headerPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, mode) + if err != nil { + return xerrors.Errorf("create file %q (mode %s): %w", headerPath, mode, err) + } + + hash := crc32.NewIEEE() + hashReader := io.TeeReader(reader, hash) + // Max file size of 10MiB. + size, err := io.CopyN(file, hashReader, 10<<20) + if xerrors.Is(err, io.EOF) { + err = nil + } + if err != nil { + _ = file.Close() + return xerrors.Errorf("copy file %q: %w", headerPath, err) + } + err = file.Close() + if err != nil { + return xerrors.Errorf("close file %q: %s", headerPath, err) + } + logger.Debug(context.Background(), "extracted file", + slog.F("size_bytes", size), + slog.F("path", headerPath), + slog.F("mode", mode), + slog.F("checksum", fmt.Sprintf("%x", hash.Sum(nil)))) + } + } + + return nil +} + +// CleanStaleSessions assumes this Layout is the latest active template version. +// Assuming that, any other template version directories found alongside it are +// considered inactive and can be removed. Inactive template versions should use +// ephemeral TerraformDirectories. +func (td Layout) CleanStaleSessions(ctx context.Context, logger slog.Logger, fs afero.Fs, now time.Time) error { + if td.ephemeral { + // Use the existing cleanup for ephemeral sessions. + return tfpath.FromWorkingDirectory(td.workDirectory).CleanStaleSessions(ctx, logger, fs, now) + } + + // All template versions share the same parent directory. Since only the latest + // active version should remain, remove all other version directories. + wd := td.WorkDirectory() + templateDir := filepath.Dir(wd) + versionDir := filepath.Base(wd) + + entries, err := afero.ReadDir(fs, templateDir) + if xerrors.Is(err, os.ErrNotExist) { + // Nothing to clean, this template dir does not exist. + return nil + } + if err != nil { + return xerrors.Errorf("can't read %q directory: %w", templateDir, err) + } + + for _, fi := range entries { + if !fi.IsDir() { + continue + } + + if fi.Name() == versionDir { + continue + } + + // Note: There is a .coder directory here with a pprof unix file. + // This is from the previous provisioner run, and will be removed here. + // TODO: Add more explicit pprof cleanup/handling. + + oldVerDir := filepath.Join(templateDir, fi.Name()) + logger.Info(ctx, "remove inactive template version directory", slog.F("version_path", oldVerDir)) + err = fs.RemoveAll(oldVerDir) + if err != nil { + logger.Error(ctx, "failed to remove inactive template version directory", slog.F("version_path", oldVerDir), slog.Error(err)) + } + } + return nil +} + +// SelectWorkspace writes the terraform workspace environment file, which acts as +// `terraform workspace select `. It is quicker than using the cli command. +// More importantly this code can be written without changing the executor +// behavior, which is nice encapsulation for this experiment. +func (td Layout) SelectWorkspace(fs afero.Fs) error { + // Also set up the terraform workspace to use + err := fs.MkdirAll(td.TerraformMetadataDir(), 0o700) + if err != nil { + return xerrors.Errorf("create terraform metadata directory %q: %w", td.TerraformMetadataDir(), err) + } + + file, err := fs.OpenFile(td.WorkspaceEnvironmentFilePath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + if err != nil { + return xerrors.Errorf("create workspace environment file: %w", err) + } + defer file.Close() + + _, err = file.WriteString(td.sessionID) + if err != nil { + _ = file.Close() + return xerrors.Errorf("write workspace environment file: %w", err) + } + return nil +} From edf056babc3932cbfeebec29765b8531b0a529fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Banaszewski?= Date: Thu, 13 Nov 2025 20:14:53 +0100 Subject: [PATCH 141/255] test: add mocked terraform installation files (#20757) Adds mocked terraform installation files and uses them in provisioner/terraform.TestInstall Fixes: https://github.com/coder/internal/issues/72 --- provisioner/terraform/install.go | 12 +- provisioner/terraform/install_test.go | 186 +++++++++++++++++++++++++- provisioner/terraform/serve.go | 2 +- 3 files changed, 190 insertions(+), 10 deletions(-) diff --git a/provisioner/terraform/install.go b/provisioner/terraform/install.go index 174fcf257d54f..19bf7c6a8dfe4 100644 --- a/provisioner/terraform/install.go +++ b/provisioner/terraform/install.go @@ -34,7 +34,7 @@ var ( // operation. // //nolint:revive // verbose is a control flag that controls the verbosity of the log output. -func Install(ctx context.Context, log slog.Logger, verbose bool, dir string, wantVersion *version.Version) (string, error) { +func Install(ctx context.Context, log slog.Logger, verbose bool, dir string, wantVersion *version.Version, baseUrl string, verifyChecksums bool) (string, error) { err := os.MkdirAll(dir, 0o750) if err != nil { return "", err @@ -63,11 +63,15 @@ func Install(ctx context.Context, log slog.Logger, verbose bool, dir string, wan } installer := &releases.ExactVersion{ - InstallDir: dir, - Product: product.Terraform, - Version: TerraformVersion, + InstallDir: dir, + Product: product.Terraform, + Version: TerraformVersion, + SkipChecksumVerification: !verifyChecksums, } installer.SetLogger(slog.Stdlib(ctx, log, slog.LevelDebug)) + if baseUrl != "" { + installer.ApiBaseURL = baseUrl + } logInstall := log.Debug if verbose { diff --git a/provisioner/terraform/install_test.go b/provisioner/terraform/install_test.go index 6a1be707dd146..edbc758043b77 100644 --- a/provisioner/terraform/install_test.go +++ b/provisioner/terraform/install_test.go @@ -6,8 +6,15 @@ package terraform_test import ( + "archive/zip" "context" + "encoding/json" + "fmt" + "net" + "net/http" "os" + "path/filepath" + "strings" "sync" "testing" "time" @@ -20,6 +27,175 @@ import ( "github.com/coder/coder/v2/testutil" ) +const ( + // simple script that mocks `./terraform version -json` + terraformExecutableTemplate = `#!/bin/bash +cat < zip contains 'terraform' binary and sometimes 'LICENSE.txt' +func createFakeTerraformInstallationFiles(t *testing.T) string { + tmpDir := t.TempDir() + + mij := mustMarshal(t, mainJSON(version1, version2)) + jv1 := mustMarshal(t, versionedJSON(version1)) + jv2 := mustMarshal(t, versionedJSON(version2)) + + // `index.json` + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "index.json"), mij, 0o400)) + + // `${version1}/index.json` + require.NoError(t, os.Mkdir(filepath.Join(tmpDir, version1.String()), 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, version1.String(), "index.json"), jv1, 0o400)) + + // `${version2}/index.json` + require.NoError(t, os.Mkdir(filepath.Join(tmpDir, version2.String()), 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, version2.String(), "index.json"), jv2, 0o400)) + + // `${version1}/linux_amd64.zip` + zip1, err := os.Create(filepath.Join(tmpDir, version1.String(), zipFilename(version1))) + require.NoError(t, err) + zip1Writer := zip.NewWriter(zip1) + + // `${version1}/linux_amd64.zip/terraform` + exe1, err := zip1Writer.Create("terraform") + require.NoError(t, err) + n, err := exe1.Write(exeContent(version1)) + require.NoError(t, err) + require.NotZero(t, n) + + // `${version1}/linux_amd64.zip/LICENSE.txt` + lic1, err := zip1Writer.Create("LICENSE.txt") + require.NoError(t, err) + n, err = lic1.Write([]byte("some license")) + require.NoError(t, err) + require.NotZero(t, n) + require.NoError(t, zip1Writer.Close()) + + // `${version2}/linux_amd64.zip` + zip2, err := os.Create(filepath.Join(tmpDir, version2.String(), zipFilename(version2))) + require.NoError(t, err) + zip2Writer := zip.NewWriter(zip2) + + // `${version1}/linux_amd64.zip/terraform` + exe2, err := zip2Writer.Create("terraform") + require.NoError(t, err) + n, err = exe2.Write(exeContent(version2)) + require.NoError(t, err) + require.NotZero(t, n) + require.NoError(t, zip2Writer.Close()) + + return tmpDir +} + +// starts http server serving fake terraform installation files +func startFakeTerraformServer(t *testing.T, tmpDir string) string { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to create listener") + } + + mux := http.NewServeMux() + fs := http.FileServer(http.Dir(tmpDir)) + mux.Handle("/terraform/", http.StripPrefix("/terraform", fs)) + + srv := http.Server{ + ReadHeaderTimeout: time.Second, + Handler: mux, + } + go srv.Serve(listener) + t.Cleanup(func() { + if err := srv.Close(); err != nil { + t.Errorf("failed to close server: %v", err) + } + }) + return "http://" + listener.Addr().String() +} + func TestInstall(t *testing.T) { t.Parallel() if testing.Short() { @@ -29,6 +205,9 @@ func TestInstall(t *testing.T) { dir := t.TempDir() log := testutil.Logger(t) + tmpDir := createFakeTerraformInstallationFiles(t) + addr := startFakeTerraformServer(t, tmpDir) + // Install spins off 8 installs with Version and waits for them all // to complete. The locking mechanism within Install should // prevent multiple binaries from being installed, so the function @@ -40,7 +219,7 @@ func TestInstall(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - p, err := terraform.Install(ctx, log, false, dir, version) + p, err := terraform.Install(ctx, log, false, dir, version, addr, false) assert.NoError(t, err) paths <- p }() @@ -60,7 +239,6 @@ func TestInstall(t *testing.T) { return firstPath } - version1 := terraform.TerraformVersion binPath := install(version1) checkBinModTime := func() time.Time { @@ -73,13 +251,11 @@ func TestInstall(t *testing.T) { modTime1 := checkBinModTime() // Since we're using the same version the install should be idempotent. - install(terraform.TerraformVersion) + install(version1) modTime2 := checkBinModTime() require.Equal(t, modTime1, modTime2) // Ensure a new install happens when version changes - version2 := version.Must(version.NewVersion("1.2.0")) - // Sanity-check require.NotEqual(t, version2.String(), version1.String()) diff --git a/provisioner/terraform/serve.go b/provisioner/terraform/serve.go index 6b14282f9f472..a927f288fae81 100644 --- a/provisioner/terraform/serve.go +++ b/provisioner/terraform/serve.go @@ -103,7 +103,7 @@ func Serve(ctx context.Context, options *ServeOptions) error { slog.F("min_version", minTerraformVersion.String())) } - binPath, err := Install(ctx, options.Logger, options.ExternalProvisioner, options.CachePath, TerraformVersion) + binPath, err := Install(ctx, options.Logger, options.ExternalProvisioner, options.CachePath, TerraformVersion, "", true) if err != nil { return xerrors.Errorf("install terraform: %w", err) } From fe3b825b86c0875898a716d2c03b087aa88af0f9 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 13 Nov 2025 14:04:12 -0600 Subject: [PATCH 142/255] chore: per template opt into cached terraform directories (#20609) For experimental and dogfood purposes, this adds the ability to opt in a single template. Leaving the rest of the templates as is. For GA, this setting might be removed or changed. --- coderd/apidoc/docs.go | 7 +++ coderd/apidoc/swagger.json | 7 +++ coderd/database/dump.sql | 6 ++- ...experimental_terraform_workspaces.down.sql | 26 ++++++++++ ...7_experimental_terraform_workspaces.up.sql | 33 ++++++++++++ coderd/database/modelqueries.go | 1 + coderd/database/models.go | 3 ++ coderd/database/queries.sql.go | 19 ++++--- coderd/database/queries/templates.sql | 3 +- .../provisionerdserver/provisionerdserver.go | 5 +- coderd/templates.go | 22 +++++--- codersdk/templates.go | 8 ++- docs/admin/security/audit-logs.md | 52 +++++++++---------- docs/reference/api/schemas.md | 8 ++- docs/reference/api/templates.md | 23 +++++--- enterprise/audit/table.go | 1 + provisionersdk/tfpath/x/tfpath.go | 2 +- site/src/api/typesGenerated.ts | 8 +++ .../TemplateSettingsForm.tsx | 36 +++++++++++++ .../TemplateSettingsPage.jest.tsx | 1 + .../TemplateSettingsPageView.stories.tsx | 2 + site/src/testHelpers/entities.ts | 1 + 22 files changed, 221 insertions(+), 53 deletions(-) create mode 100644 coderd/database/migrations/000397_experimental_terraform_workspaces.down.sql create mode 100644 coderd/database/migrations/000397_experimental_terraform_workspaces.up.sql diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 67dc8279278fa..de7cd416f287d 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -18138,6 +18138,9 @@ const docTemplate = `{ }, "use_classic_parameter_flow": { "type": "boolean" + }, + "use_terraform_workspace_cache": { + "type": "boolean" } } }, @@ -19066,6 +19069,10 @@ const docTemplate = `{ "use_classic_parameter_flow": { "description": "UseClassicParameterFlow is a flag that switches the default behavior to use the classic\nparameter flow when creating a workspace. This only affects deployments with the experiment\n\"dynamic-parameters\" enabled. This setting will live for a period after the experiment is\nmade the default.\nAn \"opt-out\" is present in case the new feature breaks some existing templates.", "type": "boolean" + }, + "use_terraform_workspace_cache": { + "description": "UseTerraformWorkspaceCache allows optionally specifying whether to use cached\nterraform directories for workspaces created from this template. This field\nonly applies when the correct experiment is enabled. This field is subject to\nbeing removed in the future.", + "type": "boolean" } } }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 1577e55b8693e..80d705f335f13 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -16613,6 +16613,9 @@ }, "use_classic_parameter_flow": { "type": "boolean" + }, + "use_terraform_workspace_cache": { + "type": "boolean" } } }, @@ -17497,6 +17500,10 @@ "use_classic_parameter_flow": { "description": "UseClassicParameterFlow is a flag that switches the default behavior to use the classic\nparameter flow when creating a workspace. This only affects deployments with the experiment\n\"dynamic-parameters\" enabled. This setting will live for a period after the experiment is\nmade the default.\nAn \"opt-out\" is present in case the new feature breaks some existing templates.", "type": "boolean" + }, + "use_terraform_workspace_cache": { + "description": "UseTerraformWorkspaceCache allows optionally specifying whether to use cached\nterraform directories for workspaces created from this template. This field\nonly applies when the correct experiment is enabled. This field is subject to\nbeing removed in the future.", + "type": "boolean" } } }, diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index fe92992de0727..c2e4091e56c96 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -2286,7 +2286,8 @@ CREATE TABLE templates ( activity_bump bigint DEFAULT '3600000000000'::bigint NOT NULL, max_port_sharing_level app_sharing_level DEFAULT 'owner'::app_sharing_level NOT NULL, use_classic_parameter_flow boolean DEFAULT false NOT NULL, - cors_behavior cors_behavior DEFAULT 'simple'::cors_behavior NOT NULL + cors_behavior cors_behavior DEFAULT 'simple'::cors_behavior NOT NULL, + use_terraform_workspace_cache boolean DEFAULT false NOT NULL ); COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.'; @@ -2309,6 +2310,8 @@ COMMENT ON COLUMN templates.deprecated IS 'If set to a non empty string, the tem COMMENT ON COLUMN templates.use_classic_parameter_flow IS 'Determines whether to default to the dynamic parameter creation flow for this template or continue using the legacy classic parameter creation flow.This is a template wide setting, the template admin can revert to the classic flow if there are any issues. An escape hatch is required, as workspace creation is a core workflow and cannot break. This column will be removed when the dynamic parameter creation flow is stable.'; +COMMENT ON COLUMN templates.use_terraform_workspace_cache IS 'Determines whether to keep terraform directories cached between runs for workspaces created from this template. When enabled, this can significantly speed up the `terraform init` step at the cost of increased disk usage. This is an opt-in experience, as it prevents modules from being updated, and therefore is a behavioral difference from the default.'; + CREATE VIEW template_with_names AS SELECT templates.id, templates.created_at, @@ -2340,6 +2343,7 @@ CREATE VIEW template_with_names AS templates.max_port_sharing_level, templates.use_classic_parameter_flow, templates.cors_behavior, + templates.use_terraform_workspace_cache, COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, COALESCE(visible_users.username, ''::text) AS created_by_username, COALESCE(visible_users.name, ''::text) AS created_by_name, diff --git a/coderd/database/migrations/000397_experimental_terraform_workspaces.down.sql b/coderd/database/migrations/000397_experimental_terraform_workspaces.down.sql new file mode 100644 index 0000000000000..394c31975a901 --- /dev/null +++ b/coderd/database/migrations/000397_experimental_terraform_workspaces.down.sql @@ -0,0 +1,26 @@ +DROP VIEW template_with_names; +-- Drop the column +ALTER TABLE templates DROP COLUMN use_terraform_workspace_cache; + +-- Update the template_with_names view by recreating it. +CREATE VIEW template_with_names AS +SELECT + templates.*, + COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS created_by_username, + COALESCE(visible_users.name, ''::text) AS created_by_name, + COALESCE(organizations.name, ''::text) AS organization_name, + COALESCE(organizations.display_name, ''::text) AS organization_display_name, + COALESCE(organizations.icon, ''::text) AS organization_icon +FROM + templates + LEFT JOIN + visible_users + ON + templates.created_by = visible_users.id + LEFT JOIN + organizations + ON templates.organization_id = organizations.id +; + +COMMENT ON VIEW template_with_names IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/migrations/000397_experimental_terraform_workspaces.up.sql b/coderd/database/migrations/000397_experimental_terraform_workspaces.up.sql new file mode 100644 index 0000000000000..3b6a57e01b5ef --- /dev/null +++ b/coderd/database/migrations/000397_experimental_terraform_workspaces.up.sql @@ -0,0 +1,33 @@ +-- Default to `false`. Users will have to manually opt into the terraform workspace cache feature. +ALTER TABLE templates ADD COLUMN use_terraform_workspace_cache BOOL NOT NULL DEFAULT false; + +COMMENT ON COLUMN templates.use_terraform_workspace_cache IS + 'Determines whether to keep terraform directories cached between runs for workspaces created from this template. ' + 'When enabled, this can significantly speed up the `terraform init` step at the cost of increased disk usage. ' + 'This is an opt-in experience, as it prevents modules from being updated, and therefore is a behavioral difference ' + 'from the default.'; + ; + +-- Update the template_with_names view by recreating it. +DROP VIEW template_with_names; +CREATE VIEW template_with_names AS +SELECT + templates.*, + COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS created_by_username, + COALESCE(visible_users.name, ''::text) AS created_by_name, + COALESCE(organizations.name, ''::text) AS organization_name, + COALESCE(organizations.display_name, ''::text) AS organization_display_name, + COALESCE(organizations.icon, ''::text) AS organization_icon +FROM + templates + LEFT JOIN + visible_users + ON + templates.created_by = visible_users.id + LEFT JOIN + organizations + ON templates.organization_id = organizations.id +; + +COMMENT ON VIEW template_with_names IS 'Joins in the display name information such as username, avatar, and organization name.'; diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index eec69671c90ab..fae0f3eca4fa4 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -127,6 +127,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, &i.CorsBehavior, + &i.UseTerraformWorkspaceCache, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, diff --git a/coderd/database/models.go b/coderd/database/models.go index 961d62d52e2b3..5cce4d95aa7f5 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -4294,6 +4294,7 @@ type Template struct { MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"` UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"` CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"` + UseTerraformWorkspaceCache bool `db:"use_terraform_workspace_cache" json:"use_terraform_workspace_cache"` CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` CreatedByName string `db:"created_by_name" json:"created_by_name"` @@ -4343,6 +4344,8 @@ type TemplateTable struct { // Determines whether to default to the dynamic parameter creation flow for this template or continue using the legacy classic parameter creation flow.This is a template wide setting, the template admin can revert to the classic flow if there are any issues. An escape hatch is required, as workspace creation is a core workflow and cannot break. This column will be removed when the dynamic parameter creation flow is stable. UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"` CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"` + // Determines whether to keep terraform directories cached between runs for workspaces created from this template. When enabled, this can significantly speed up the `terraform init` step at the cost of increased disk usage. This is an opt-in experience, as it prevents modules from being updated, and therefore is a behavioral difference from the default. + UseTerraformWorkspaceCache bool `db:"use_terraform_workspace_cache" json:"use_terraform_workspace_cache"` } // Records aggregated usage statistics for templates/users. All usage is rounded up to the nearest minute. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 0f761697d3d43..4e8f3d36b2bc7 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -13522,7 +13522,7 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, templateID const getTemplateByID = `-- name: GetTemplateByID :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, use_terraform_workspace_cache, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names WHERE @@ -13565,6 +13565,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, &i.CorsBehavior, + &i.UseTerraformWorkspaceCache, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -13577,7 +13578,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, use_terraform_workspace_cache, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates WHERE @@ -13628,6 +13629,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, &i.CorsBehavior, + &i.UseTerraformWorkspaceCache, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -13639,7 +13641,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G } const getTemplates = `-- name: GetTemplates :many -SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates +SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, use_terraform_workspace_cache, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates ORDER BY (name, id) ASC ` @@ -13683,6 +13685,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, &i.CorsBehavior, + &i.UseTerraformWorkspaceCache, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -13705,7 +13708,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many SELECT - t.id, t.created_at, t.updated_at, t.organization_id, t.deleted, t.name, t.provisioner, t.active_version_id, t.description, t.default_ttl, t.created_by, t.icon, t.user_acl, t.group_acl, t.display_name, t.allow_user_cancel_workspace_jobs, t.allow_user_autostart, t.allow_user_autostop, t.failure_ttl, t.time_til_dormant, t.time_til_dormant_autodelete, t.autostop_requirement_days_of_week, t.autostop_requirement_weeks, t.autostart_block_days_of_week, t.require_active_version, t.deprecated, t.activity_bump, t.max_port_sharing_level, t.use_classic_parameter_flow, t.cors_behavior, t.created_by_avatar_url, t.created_by_username, t.created_by_name, t.organization_name, t.organization_display_name, t.organization_icon + t.id, t.created_at, t.updated_at, t.organization_id, t.deleted, t.name, t.provisioner, t.active_version_id, t.description, t.default_ttl, t.created_by, t.icon, t.user_acl, t.group_acl, t.display_name, t.allow_user_cancel_workspace_jobs, t.allow_user_autostart, t.allow_user_autostop, t.failure_ttl, t.time_til_dormant, t.time_til_dormant_autodelete, t.autostop_requirement_days_of_week, t.autostop_requirement_weeks, t.autostart_block_days_of_week, t.require_active_version, t.deprecated, t.activity_bump, t.max_port_sharing_level, t.use_classic_parameter_flow, t.cors_behavior, t.use_terraform_workspace_cache, t.created_by_avatar_url, t.created_by_username, t.created_by_name, t.organization_name, t.organization_display_name, t.organization_icon FROM template_with_names AS t LEFT JOIN @@ -13864,6 +13867,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, &i.CorsBehavior, + &i.UseTerraformWorkspaceCache, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -14049,7 +14053,8 @@ SET group_acl = $8, max_port_sharing_level = $9, use_classic_parameter_flow = $10, - cors_behavior = $11 + cors_behavior = $11, + use_terraform_workspace_cache = $12 WHERE id = $1 ` @@ -14066,6 +14071,7 @@ type UpdateTemplateMetaByIDParams struct { MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"` UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"` CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"` + UseTerraformWorkspaceCache bool `db:"use_terraform_workspace_cache" json:"use_terraform_workspace_cache"` } func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error { @@ -14081,6 +14087,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl arg.MaxPortSharingLevel, arg.UseClassicParameterFlow, arg.CorsBehavior, + arg.UseTerraformWorkspaceCache, ) return err } @@ -22370,7 +22377,7 @@ LEFT JOIN LATERAL ( ) latest_build ON TRUE LEFT JOIN LATERAL ( SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, use_terraform_workspace_cache FROM templates WHERE diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index 43f1aea6c561f..4de4e2fadbebd 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -173,7 +173,8 @@ SET group_acl = $8, max_port_sharing_level = $9, use_classic_parameter_flow = $10, - cors_behavior = $11 + cors_behavior = $11, + use_terraform_workspace_cache = $12 WHERE id = $1 ; diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index ce58d90468026..5ea01afcac829 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -711,7 +711,10 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo ExternalAuthProviders: externalAuthProviders, // If active and experiment is enabled, allow workspace reuse existing TF // workspaces (directories) for a faster startup. - ExpReuseTerraformWorkspace: ptr.Ref(activeVersion && s.Experiments.Enabled(codersdk.ExperimentTerraformWorkspace)), + ExpReuseTerraformWorkspace: ptr.Ref(s.Experiments.Enabled(codersdk.ExperimentTerraformWorkspace) && // Experiment required + template.UseTerraformWorkspaceCache && // Template setting + activeVersion, // Only for active versions + ), Metadata: &sdkproto.Metadata{ CoderUrl: s.AccessURL.String(), WorkspaceTransition: transition, diff --git a/coderd/templates.go b/coderd/templates.go index c93d0debf06e7..39892aa5fef8c 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -773,6 +773,11 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { classicTemplateFlow = *req.UseClassicParameterFlow } + useTerraformWorkspaceCache := template.UseTerraformWorkspaceCache + if req.UseTerraformWorkspaceCache != nil { + useTerraformWorkspaceCache = *req.UseTerraformWorkspaceCache + } + displayName := ptr.NilToDefault(req.DisplayName, template.DisplayName) description := ptr.NilToDefault(req.Description, template.Description) icon := ptr.NilToDefault(req.Icon, template.Icon) @@ -798,7 +803,8 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { (deprecationMessage == template.Deprecated) && (classicTemplateFlow == template.UseClassicParameterFlow) && maxPortShareLevel == template.MaxPortSharingLevel && - corsBehavior == template.CorsBehavior { + corsBehavior == template.CorsBehavior && + useTerraformWorkspaceCache == template.UseTerraformWorkspaceCache { return nil } @@ -841,6 +847,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { MaxPortSharingLevel: maxPortShareLevel, UseClassicParameterFlow: classicTemplateFlow, CorsBehavior: corsBehavior, + UseTerraformWorkspaceCache: useTerraformWorkspaceCache, }) if err != nil { return xerrors.Errorf("update template metadata: %w", err) @@ -1119,12 +1126,13 @@ func (api *API) convertTemplate( DaysOfWeek: codersdk.BitmapToWeekdays(template.AutostartAllowedDays()), }, // These values depend on entitlements and come from the templateAccessControl - RequireActiveVersion: templateAccessControl.RequireActiveVersion, - Deprecated: templateAccessControl.IsDeprecated(), - DeprecationMessage: templateAccessControl.Deprecated, - MaxPortShareLevel: maxPortShareLevel, - UseClassicParameterFlow: template.UseClassicParameterFlow, - CORSBehavior: codersdk.CORSBehavior(template.CorsBehavior), + RequireActiveVersion: templateAccessControl.RequireActiveVersion, + Deprecated: templateAccessControl.IsDeprecated(), + DeprecationMessage: templateAccessControl.Deprecated, + MaxPortShareLevel: maxPortShareLevel, + UseClassicParameterFlow: template.UseClassicParameterFlow, + UseTerraformWorkspaceCache: template.UseTerraformWorkspaceCache, + CORSBehavior: codersdk.CORSBehavior(template.CorsBehavior), } } diff --git a/codersdk/templates.go b/codersdk/templates.go index 49c1f9e7c57f9..a96dcb495dad8 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -63,7 +63,8 @@ type Template struct { MaxPortShareLevel WorkspaceAgentPortShareLevel `json:"max_port_share_level"` CORSBehavior CORSBehavior `json:"cors_behavior"` - UseClassicParameterFlow bool `json:"use_classic_parameter_flow"` + UseClassicParameterFlow bool `json:"use_classic_parameter_flow"` + UseTerraformWorkspaceCache bool `json:"use_terraform_workspace_cache"` } // WeekdaysToBitmap converts a list of weekdays to a bitmap in accordance with @@ -263,6 +264,11 @@ type UpdateTemplateMeta struct { // made the default. // An "opt-out" is present in case the new feature breaks some existing templates. UseClassicParameterFlow *bool `json:"use_classic_parameter_flow,omitempty"` + // UseTerraformWorkspaceCache allows optionally specifying whether to use cached + // terraform directories for workspaces created from this template. This field + // only applies when the correct experiment is enabled. This field is subject to + // being removed in the future. + UseTerraformWorkspaceCache *bool `json:"use_terraform_workspace_cache,omitempty"` } type TemplateExample struct { diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index a5b12f4e87449..381bb61ddbce1 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -13,32 +13,32 @@ We track the following resources: -| Resource | | | -|----------------------------------------------------------|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| APIKey
login, logout, register, create, delete | |
FieldTracked
allow_listfalse
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopesfalse
token_namefalse
updated_atfalse
user_idtrue
| -| AuditOAuthConvertState
| |
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| -| Group
create, write, delete | |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| -| AuditableOrganizationMember
| |
FieldTracked
created_attrue
organization_idfalse
rolestrue
updated_attrue
user_idtrue
usernametrue
| -| CustomRole
| |
FieldTracked
created_atfalse
display_nametrue
idfalse
nametrue
org_permissionstrue
organization_idfalse
site_permissionstrue
updated_atfalse
user_permissionstrue
| -| GitSSHKey
create | |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| -| GroupSyncSettings
| |
FieldTracked
auto_create_missing_groupstrue
fieldtrue
legacy_group_name_mappingfalse
mappingtrue
regex_filtertrue
| -| HealthSettings
| |
FieldTracked
dismissed_healthcheckstrue
idfalse
| -| License
create, delete | |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| -| NotificationTemplate
| |
FieldTracked
actionstrue
body_templatetrue
enabled_by_defaulttrue
grouptrue
idfalse
kindtrue
methodtrue
nametrue
title_templatetrue
| -| NotificationsSettings
| |
FieldTracked
idfalse
notifier_pausedtrue
| -| OAuth2ProviderApp
| |
FieldTracked
callback_urltrue
client_id_issued_atfalse
client_secret_expires_attrue
client_typetrue
client_uritrue
contactstrue
created_atfalse
dynamically_registeredtrue
grant_typestrue
icontrue
idfalse
jwkstrue
jwks_uritrue
logo_uritrue
nametrue
policy_uritrue
redirect_uristrue
registration_access_tokentrue
registration_client_uritrue
response_typestrue
scopetrue
software_idtrue
software_versiontrue
token_endpoint_auth_methodtrue
tos_uritrue
updated_atfalse
| -| OAuth2ProviderAppSecret
| |
FieldTracked
app_idfalse
created_atfalse
display_secretfalse
hashed_secretfalse
idfalse
last_used_atfalse
secret_prefixfalse
| -| Organization
| |
FieldTracked
created_atfalse
deletedtrue
descriptiontrue
display_nametrue
icontrue
idfalse
is_defaulttrue
nametrue
updated_attrue
| -| OrganizationSyncSettings
| |
FieldTracked
assign_defaulttrue
fieldtrue
mappingtrue
| -| PrebuildsSettings
| |
FieldTracked
idfalse
reconciliation_pausedtrue
| -| RoleSyncSettings
| |
FieldTracked
fieldtrue
mappingtrue
| -| TaskTable
| |
FieldTracked
created_atfalse
deleted_atfalse
idtrue
nametrue
organization_idfalse
owner_idtrue
prompttrue
template_parameterstrue
template_version_idtrue
workspace_idtrue
| -| Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
cors_behaviortrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
use_classic_parameter_flowtrue
user_acltrue
| -| TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_ai_taskfalse
has_external_agentfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| -| User
create, write, delete | |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
is_systemtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| -| WorkspaceBuild
start, stop | |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
has_ai_taskfalse
has_external_agentfalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_namefalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| -| WorkspaceProxy
| |
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| -| WorkspaceTable
| |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
group_acltrue
idtrue
last_used_atfalse
nametrue
next_start_attrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
user_acltrue
| +| Resource | | | +|----------------------------------------------------------|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| APIKey
login, logout, register, create, delete | |
FieldTracked
allow_listfalse
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopesfalse
token_namefalse
updated_atfalse
user_idtrue
| +| AuditOAuthConvertState
| |
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| +| Group
create, write, delete | |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| +| AuditableOrganizationMember
| |
FieldTracked
created_attrue
organization_idfalse
rolestrue
updated_attrue
user_idtrue
usernametrue
| +| CustomRole
| |
FieldTracked
created_atfalse
display_nametrue
idfalse
nametrue
org_permissionstrue
organization_idfalse
site_permissionstrue
updated_atfalse
user_permissionstrue
| +| GitSSHKey
create | |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| +| GroupSyncSettings
| |
FieldTracked
auto_create_missing_groupstrue
fieldtrue
legacy_group_name_mappingfalse
mappingtrue
regex_filtertrue
| +| HealthSettings
| |
FieldTracked
dismissed_healthcheckstrue
idfalse
| +| License
create, delete | |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| +| NotificationTemplate
| |
FieldTracked
actionstrue
body_templatetrue
enabled_by_defaulttrue
grouptrue
idfalse
kindtrue
methodtrue
nametrue
title_templatetrue
| +| NotificationsSettings
| |
FieldTracked
idfalse
notifier_pausedtrue
| +| OAuth2ProviderApp
| |
FieldTracked
callback_urltrue
client_id_issued_atfalse
client_secret_expires_attrue
client_typetrue
client_uritrue
contactstrue
created_atfalse
dynamically_registeredtrue
grant_typestrue
icontrue
idfalse
jwkstrue
jwks_uritrue
logo_uritrue
nametrue
policy_uritrue
redirect_uristrue
registration_access_tokentrue
registration_client_uritrue
response_typestrue
scopetrue
software_idtrue
software_versiontrue
token_endpoint_auth_methodtrue
tos_uritrue
updated_atfalse
| +| OAuth2ProviderAppSecret
| |
FieldTracked
app_idfalse
created_atfalse
display_secretfalse
hashed_secretfalse
idfalse
last_used_atfalse
secret_prefixfalse
| +| Organization
| |
FieldTracked
created_atfalse
deletedtrue
descriptiontrue
display_nametrue
icontrue
idfalse
is_defaulttrue
nametrue
updated_attrue
| +| OrganizationSyncSettings
| |
FieldTracked
assign_defaulttrue
fieldtrue
mappingtrue
| +| PrebuildsSettings
| |
FieldTracked
idfalse
reconciliation_pausedtrue
| +| RoleSyncSettings
| |
FieldTracked
fieldtrue
mappingtrue
| +| TaskTable
| |
FieldTracked
created_atfalse
deleted_atfalse
idtrue
nametrue
organization_idfalse
owner_idtrue
prompttrue
template_parameterstrue
template_version_idtrue
workspace_idtrue
| +| Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
cors_behaviortrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
use_classic_parameter_flowtrue
use_terraform_workspace_cachetrue
user_acltrue
| +| TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_ai_taskfalse
has_external_agentfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| +| User
create, write, delete | |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
is_systemtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| +| WorkspaceBuild
start, stop | |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
has_ai_taskfalse
has_external_agentfalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_namefalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| +| WorkspaceProxy
| |
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| +| WorkspaceTable
| |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
group_acltrue
idtrue
last_used_atfalse
nametrue
next_start_attrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
user_acltrue
| diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index e466abaa6fba0..0f43255ad60c7 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -8119,7 +8119,8 @@ Only certain features set these fields: - FeatureManagedAgentLimit| "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z", - "use_classic_parameter_flow": true + "use_classic_parameter_flow": true, + "use_terraform_workspace_cache": true } ``` @@ -8160,6 +8161,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit| | `time_til_dormant_ms` | integer | false | | | | `updated_at` | string | false | | | | `use_classic_parameter_flow` | boolean | false | | | +| `use_terraform_workspace_cache` | boolean | false | | | #### Enumerated Values @@ -9211,7 +9213,8 @@ Restarts will only happen on weekdays in this list on weeks which line up with W "time_til_dormant_ms": 0, "update_workspace_dormant_at": true, "update_workspace_last_used_at": true, - "use_classic_parameter_flow": true + "use_classic_parameter_flow": true, + "use_terraform_workspace_cache": true } ``` @@ -9241,6 +9244,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W | `update_workspace_dormant_at` | boolean | false | | Update workspace dormant at updates the dormant_at field of workspaces spawned from the template. This is useful for preventing dormant workspaces being immediately deleted when updating the dormant_ttl field to a new, shorter value. | | `update_workspace_last_used_at` | boolean | false | | Update workspace last used at updates the last_used_at field of workspaces spawned from the template. This is useful for preventing workspaces being immediately locked when updating the inactivity_ttl field to a new, shorter value. | | `use_classic_parameter_flow` | boolean | false | | Use classic parameter flow is a flag that switches the default behavior to use the classic parameter flow when creating a workspace. This only affects deployments with the experiment "dynamic-parameters" enabled. This setting will live for a period after the experiment is made the default. An "opt-out" is present in case the new feature breaks some existing templates. | +| `use_terraform_workspace_cache` | boolean | false | | Use terraform workspace cache allows optionally specifying whether to use cached terraform directories for workspaces created from this template. This field only applies when the correct experiment is enabled. This field is subject to being removed in the future. | ## codersdk.UpdateUserAppearanceSettingsRequest diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index 2c516f4788b4d..7849b79957006 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -80,7 +80,8 @@ To include deprecated templates, specify `deprecated:true` in the search query. "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z", - "use_classic_parameter_flow": true + "use_classic_parameter_flow": true, + "use_terraform_workspace_cache": true } ] ``` @@ -138,6 +139,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W |`» time_til_dormant_ms`|integer|false||| |`» updated_at`|string(date-time)|false||| |`» use_classic_parameter_flow`|boolean|false||| +|`» use_terraform_workspace_cache`|boolean|false||| #### Enumerated Values @@ -266,7 +268,8 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z", - "use_classic_parameter_flow": true + "use_classic_parameter_flow": true, + "use_terraform_workspace_cache": true } ``` @@ -416,7 +419,8 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z", - "use_classic_parameter_flow": true + "use_classic_parameter_flow": true, + "use_terraform_workspace_cache": true } ``` @@ -832,7 +836,8 @@ To include deprecated templates, specify `deprecated:true` in the search query. "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z", - "use_classic_parameter_flow": true + "use_classic_parameter_flow": true, + "use_terraform_workspace_cache": true } ] ``` @@ -890,6 +895,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W |`» time_til_dormant_ms`|integer|false||| |`» updated_at`|string(date-time)|false||| |`» use_classic_parameter_flow`|boolean|false||| +|`» use_terraform_workspace_cache`|boolean|false||| #### Enumerated Values @@ -1036,7 +1042,8 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \ "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z", - "use_classic_parameter_flow": true + "use_classic_parameter_flow": true, + "use_terraform_workspace_cache": true } ``` @@ -1140,7 +1147,8 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ "time_til_dormant_ms": 0, "update_workspace_dormant_at": true, "update_workspace_last_used_at": true, - "use_classic_parameter_flow": true + "use_classic_parameter_flow": true, + "use_terraform_workspace_cache": true } ``` @@ -1207,7 +1215,8 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ "time_til_dormant_autodelete_ms": 0, "time_til_dormant_ms": 0, "updated_at": "2019-08-24T14:15:22Z", - "use_classic_parameter_flow": true + "use_classic_parameter_flow": true, + "use_terraform_workspace_cache": true } ``` diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index e09f0b5876268..e5661687a1ddb 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -117,6 +117,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "activity_bump": ActionTrack, "use_classic_parameter_flow": ActionTrack, "cors_behavior": ActionTrack, + "use_terraform_workspace_cache": ActionTrack, }, &database.TemplateVersion{}: { "id": ActionTrack, diff --git a/provisionersdk/tfpath/x/tfpath.go b/provisionersdk/tfpath/x/tfpath.go index bc91315324158..c6b9f5d669e94 100644 --- a/provisionersdk/tfpath/x/tfpath.go +++ b/provisionersdk/tfpath/x/tfpath.go @@ -1,7 +1,7 @@ package x // This file will replace the `tfpath.go` in the parent `tfpath` package when the -// `terraform-workspace` experiment is graduated. +// `terraform-directory-reuse` experiment is graduated. import ( "archive/tar" diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index ea65957316e53..c2c94aa314b3d 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -4922,6 +4922,7 @@ export interface Template { readonly max_port_share_level: WorkspaceAgentPortShareLevel; readonly cors_behavior: CORSBehavior; readonly use_classic_parameter_flow: boolean; + readonly use_terraform_workspace_cache: boolean; } // From codersdk/templates.go @@ -5443,6 +5444,13 @@ export interface UpdateTemplateMeta { * An "opt-out" is present in case the new feature breaks some existing templates. */ readonly use_classic_parameter_flow?: boolean; + /** + * UseTerraformWorkspaceCache allows optionally specifying whether to use cached + * terraform directories for workspaces created from this template. This field + * only applies when the correct experiment is enabled. This field is subject to + * being removed in the future. + */ + readonly use_terraform_workspace_cache?: boolean; } // From codersdk/users.go diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index 5b35b5ba26f14..e1ed15d198a0e 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -26,6 +26,7 @@ import { StackLabelHelperText, } from "components/StackLabel/StackLabel"; import { type FormikTouched, useFormik } from "formik"; +import { useDashboard } from "modules/dashboard/useDashboard"; import type { FC } from "react"; import { docs } from "utils/docs"; import { @@ -96,12 +97,14 @@ export const TemplateSettingsForm: FC = ({ max_port_share_level: template.max_port_share_level, use_classic_parameter_flow: template.use_classic_parameter_flow, cors_behavior: template.cors_behavior, + use_terraform_workspace_cache: template.use_terraform_workspace_cache, }, validationSchema, onSubmit, initialTouched, }); const getFieldHelpers = getFormHelpers(form, error); + const { experiments } = useDashboard(); return ( = ({ } /> + {experiments.includes("terraform-directory-reuse") && ( + + } + label={ + + + Enable Terraform directory caching on provisioners + + +
+ When enabled, the provisioner reuses the .terraform + directory for all workspace builds using the active + version. This significantly reduces Terraform init time by + caching module and provider downloads.{" "} + + Unpinned modules may cause inconsistent builds between + provisioners. + +
+
+
+ } + /> + )} diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.jest.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.jest.tsx index 2ed53059665bf..f9ac0553f90f4 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.jest.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.jest.tsx @@ -56,6 +56,7 @@ const validFormValues: FormValues = { max_port_share_level: "owner", use_classic_parameter_flow: true, cors_behavior: "simple", + use_terraform_workspace_cache: false, }; const renderTemplateSettingsPage = async () => { diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPageView.stories.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPageView.stories.tsx index 5d65ffaf15c5a..a17b7cbb31b6c 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPageView.stories.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPageView.stories.tsx @@ -1,4 +1,5 @@ import { MockTemplate, mockApiError } from "testHelpers/entities"; +import { withDashboardProvider } from "testHelpers/storybook"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { action } from "storybook/actions"; import { TemplateSettingsPageView } from "./TemplateSettingsPageView"; @@ -12,6 +13,7 @@ const meta: Meta = { advancedSchedulingEnabled: true, onCancel: action("onCancel"), }, + decorators: [withDashboardProvider], }; export default meta; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 03d108b859648..5df216d769e21 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -855,6 +855,7 @@ export const MockTemplate: TypesGen.Template = { max_port_share_level: "public", use_classic_parameter_flow: false, cors_behavior: "simple", + use_terraform_workspace_cache: false, }; const _MockTemplateVersionFiles: TemplateVersionFiles = { From b31d09865ec342b3acb19c942eba903d79125c62 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:57:22 +1100 Subject: [PATCH 143/255] feat(scaletest): add runner for prebuilds (#20571) Relates to https://github.com/coder/internal/issues/914 Adds a runner for scaletesting prebuilds. The runner uploads a no-op template with prebuilds, watches for the corresponding workspaces to be created, and then does the same to tear them down. I didn't originally plan on having metrics for the teardown, but I figured we might as well as it's still the same prebuilds reconciliation loop mechanism being tested. --- enterprise/scaletest/prebuilds/run_test.go | 141 +++++++++ scaletest/dynamicparameters/template.go | 48 +-- scaletest/loadtestutil/files.go | 50 +++ scaletest/prebuilds/config.go | 86 ++++++ scaletest/prebuilds/metrics.go | 125 ++++++++ scaletest/prebuilds/run.go | 343 +++++++++++++++++++++ scaletest/prebuilds/tf/main.tf.tpl | 18 ++ 7 files changed, 765 insertions(+), 46 deletions(-) create mode 100644 enterprise/scaletest/prebuilds/run_test.go create mode 100644 scaletest/loadtestutil/files.go create mode 100644 scaletest/prebuilds/config.go create mode 100644 scaletest/prebuilds/metrics.go create mode 100644 scaletest/prebuilds/run.go create mode 100644 scaletest/prebuilds/tf/main.tf.tpl diff --git a/enterprise/scaletest/prebuilds/run_test.go b/enterprise/scaletest/prebuilds/run_test.go new file mode 100644 index 0000000000000..4334d0c0961bc --- /dev/null +++ b/enterprise/scaletest/prebuilds/run_test.go @@ -0,0 +1,141 @@ +package prebuilds_test + +import ( + "io" + "strconv" + "sync" + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" + "github.com/coder/coder/v2/enterprise/coderd/license" + "github.com/coder/coder/v2/scaletest/prebuilds" + "github.com/coder/coder/v2/testutil" + "github.com/coder/quartz" +) + +func TestRun(t *testing.T) { + t.Parallel() + + t.Skip("This test takes several minutes to run, and is intended as a manual regression test") + + ctx := testutil.Context(t, testutil.WaitSuperLong*3) + + client, user := coderdenttest.New(t, &coderdenttest.Options{ + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureWorkspacePrebuilds: 1, + codersdk.FeatureExternalProvisionerDaemons: 1, + }, + }, + }) + + // This is a real Terraform provisioner + _ = coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, user.OrganizationID, nil) + + numTemplates := 2 + numPresets := 1 + numPresetPrebuilds := 1 + + //nolint:gocritic // It's fine to use the owner user to pause prebuilds + err := client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{ + ReconciliationPaused: true, + }) + require.NoError(t, err) + + setupBarrier := new(sync.WaitGroup) + setupBarrier.Add(numTemplates) + creationBarrier := new(sync.WaitGroup) + creationBarrier.Add(numTemplates) + deletionSetupBarrier := new(sync.WaitGroup) + deletionSetupBarrier.Add(1) + deletionBarrier := new(sync.WaitGroup) + deletionBarrier.Add(numTemplates) + + metrics := prebuilds.NewMetrics(prometheus.NewRegistry()) + + eg, runCtx := errgroup.WithContext(ctx) + + runners := make([]*prebuilds.Runner, 0, numTemplates) + for i := range numTemplates { + cfg := prebuilds.Config{ + OrganizationID: user.OrganizationID, + NumPresets: numPresets, + NumPresetPrebuilds: numPresetPrebuilds, + TemplateVersionJobTimeout: testutil.WaitSuperLong * 2, + PrebuildWorkspaceTimeout: testutil.WaitSuperLong * 2, + Metrics: metrics, + SetupBarrier: setupBarrier, + CreationBarrier: creationBarrier, + DeletionSetupBarrier: deletionSetupBarrier, + DeletionBarrier: deletionBarrier, + Clock: quartz.NewReal(), + } + err := cfg.Validate() + require.NoError(t, err) + + runner := prebuilds.NewRunner(client, cfg) + runners = append(runners, runner) + eg.Go(func() error { + return runner.Run(runCtx, strconv.Itoa(i), io.Discard) + }) + } + + // Wait for all runners to reach the setup barrier (templates created) + setupBarrier.Wait() + + // Resume prebuilds to trigger prebuild creation + err = client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{ + ReconciliationPaused: false, + }) + require.NoError(t, err) + + // Wait for all runners to reach the creation barrier (prebuilds created) + creationBarrier.Wait() + + //nolint:gocritic // Owner user is fine here as we want to view all workspaces + workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{}) + require.NoError(t, err) + expectedWorkspaces := numTemplates * numPresets * numPresetPrebuilds + require.Equal(t, workspaces.Count, expectedWorkspaces) + + // Pause prebuilds before deletion setup + err = client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{ + ReconciliationPaused: true, + }) + require.NoError(t, err) + + // Signal runners that prebuilds are paused and they can prepare for deletion + deletionSetupBarrier.Done() + + // Wait for all runners to reach the deletion barrier (template versions updated to 0 prebuilds) + deletionBarrier.Wait() + + // Resume prebuilds to trigger prebuild deletion + err = client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{ + ReconciliationPaused: false, + }) + require.NoError(t, err) + + err = eg.Wait() + require.NoError(t, err) + + //nolint:gocritic // Owner user is fine here as we want to view all workspaces + workspaces, err = client.Workspaces(ctx, codersdk.WorkspaceFilter{}) + require.NoError(t, err) + require.Equal(t, workspaces.Count, 0) + + cleanupEg, cleanupCtx := errgroup.WithContext(ctx) + for i, runner := range runners { + cleanupEg.Go(func() error { + return runner.Cleanup(cleanupCtx, strconv.Itoa(i), io.Discard) + }) + } + + err = cleanupEg.Wait() + require.NoError(t, err) +} diff --git a/scaletest/dynamicparameters/template.go b/scaletest/dynamicparameters/template.go index 5faf67e531320..dbe4b079b1504 100644 --- a/scaletest/dynamicparameters/template.go +++ b/scaletest/dynamicparameters/template.go @@ -1,15 +1,12 @@ package dynamicparameters import ( - "archive/tar" "bytes" "context" _ "embed" "encoding/json" "fmt" "io" - "path/filepath" - "slices" "strings" "text/template" "time" @@ -20,6 +17,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/cryptorand" + "github.com/coder/coder/v2/scaletest/loadtestutil" "github.com/coder/quartz" ) @@ -89,48 +87,6 @@ func GetModuleFiles() map[string][]byte { } } -func createTarFromFiles(files map[string][]byte) ([]byte, error) { - buf := new(bytes.Buffer) - writer := tar.NewWriter(buf) - dirs := []string{} - for name, content := range files { - // We need to add directories before any files that use them. But, we only need to do this - // once. - dir := filepath.Dir(name) - if dir != "." && !slices.Contains(dirs, dir) { - dirs = append(dirs, dir) - err := writer.WriteHeader(&tar.Header{ - Name: dir, - Mode: 0o755, - Typeflag: tar.TypeDir, - }) - if err != nil { - return nil, err - } - } - - err := writer.WriteHeader(&tar.Header{ - Name: name, - Size: int64(len(content)), - Mode: 0o644, - }) - if err != nil { - return nil, err - } - - _, err = writer.Write(content) - if err != nil { - return nil, err - } - } - // `writer.Close()` function flushes the writer buffer, and adds extra padding to create a legal tarball. - err := writer.Close() - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - func TemplateTarData() ([]byte, error) { mainTF, err := TemplateContent() if err != nil { @@ -144,7 +100,7 @@ func TemplateTarData() ([]byte, error) { for k, v := range moduleFiles { files[k] = v } - tarData, err := createTarFromFiles(files) + tarData, err := loadtestutil.CreateTarFromFiles(files) if err != nil { return nil, xerrors.Errorf("failed to create tarball: %w", err) } diff --git a/scaletest/loadtestutil/files.go b/scaletest/loadtestutil/files.go new file mode 100644 index 0000000000000..2890700f4efd5 --- /dev/null +++ b/scaletest/loadtestutil/files.go @@ -0,0 +1,50 @@ +package loadtestutil + +import ( + "archive/tar" + "bytes" + "path/filepath" + "slices" +) + +func CreateTarFromFiles(files map[string][]byte) ([]byte, error) { + buf := new(bytes.Buffer) + writer := tar.NewWriter(buf) + dirs := []string{} + for name, content := range files { + // We need to add directories before any files that use them. But, we only need to do this + // once. + dir := filepath.Dir(name) + if dir != "." && !slices.Contains(dirs, dir) { + dirs = append(dirs, dir) + err := writer.WriteHeader(&tar.Header{ + Name: dir, + Mode: 0o755, + Typeflag: tar.TypeDir, + }) + if err != nil { + return nil, err + } + } + + err := writer.WriteHeader(&tar.Header{ + Name: name, + Size: int64(len(content)), + Mode: 0o644, + }) + if err != nil { + return nil, err + } + + _, err = writer.Write(content) + if err != nil { + return nil, err + } + } + // `writer.Close()` function flushes the writer buffer, and adds extra padding to create a legal tarball. + err := writer.Close() + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/scaletest/prebuilds/config.go b/scaletest/prebuilds/config.go new file mode 100644 index 0000000000000..05f1fc48ad85e --- /dev/null +++ b/scaletest/prebuilds/config.go @@ -0,0 +1,86 @@ +package prebuilds + +import ( + "sync" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/coder/quartz" +) + +type Config struct { + // OrganizationID is the ID of the organization to create the prebuilds in. + OrganizationID uuid.UUID `json:"organization_id"` + // NumPresets is the number of presets the template should have. + NumPresets int `json:"num_presets"` + // NumPresetPrebuilds is the number of prebuilds per preset. + // Total prebuilds = NumPresets * NumPresetPrebuilds + NumPresetPrebuilds int `json:"num_preset_prebuilds"` + + // TemplateVersionJobTimeout is how long to wait for template version + // provisioning jobs to complete. + TemplateVersionJobTimeout time.Duration `json:"template_version_job_timeout"` + + // PrebuildWorkspaceTimeout is how long to wait for all prebuild + // workspaces to be created and completed. + PrebuildWorkspaceTimeout time.Duration `json:"prebuild_workspace_timeout"` + + Metrics *Metrics `json:"-"` + + // SetupBarrier is used to ensure all templates have been created + // before unpausing prebuilds. + SetupBarrier *sync.WaitGroup `json:"-"` + + // CreationBarrier is used to ensure all prebuild creation has completed + // before pausing prebuilds for deletion. + CreationBarrier *sync.WaitGroup `json:"-"` + + // DeletionSetupBarrier is used by the runner owner (CLI/test) to signal when + // prebuilds have been paused, allowing runners to create new template versions + // with 0 prebuilds. Only the owner calls Done(), runners only Wait(). + DeletionSetupBarrier *sync.WaitGroup `json:"-"` + + // DeletionBarrier is used to ensure all templates have been updated + // with 0 prebuilds before resuming prebuilds. + DeletionBarrier *sync.WaitGroup `json:"-"` + + Clock quartz.Clock `json:"-"` +} + +func (c Config) Validate() error { + if c.TemplateVersionJobTimeout <= 0 { + return xerrors.New("template_version_job_timeout must be greater than 0") + } + + if c.PrebuildWorkspaceTimeout <= 0 { + return xerrors.New("prebuild_workspace_timeout must be greater than 0") + } + + if c.SetupBarrier == nil { + return xerrors.New("setup barrier must be set") + } + + if c.CreationBarrier == nil { + return xerrors.New("creation barrier must be set") + } + + if c.DeletionSetupBarrier == nil { + return xerrors.New("deletion setup barrier must be set") + } + + if c.DeletionBarrier == nil { + return xerrors.New("deletion barrier must be set") + } + + if c.Metrics == nil { + return xerrors.New("metrics must be set") + } + + if c.Clock == nil { + return xerrors.New("clock must be set") + } + + return nil +} diff --git a/scaletest/prebuilds/metrics.go b/scaletest/prebuilds/metrics.go new file mode 100644 index 0000000000000..553b874e2d3ec --- /dev/null +++ b/scaletest/prebuilds/metrics.go @@ -0,0 +1,125 @@ +package prebuilds + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +type Metrics struct { + PrebuildJobsCreated prometheus.GaugeVec + PrebuildJobsRunning prometheus.GaugeVec + PrebuildJobsFailed prometheus.GaugeVec + PrebuildJobsCompleted prometheus.GaugeVec + + PrebuildDeletionJobsCreated prometheus.GaugeVec + PrebuildDeletionJobsRunning prometheus.GaugeVec + PrebuildDeletionJobsFailed prometheus.GaugeVec + PrebuildDeletionJobsCompleted prometheus.GaugeVec + + PrebuildErrorsTotal prometheus.CounterVec +} + +func NewMetrics(reg prometheus.Registerer) *Metrics { + m := &Metrics{ + PrebuildJobsCreated: *prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "prebuild_jobs_created", + Help: "Number of prebuild jobs that have been created.", + }, []string{"template_name"}), + PrebuildJobsRunning: *prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "prebuild_jobs_running", + Help: "Number of prebuild jobs that are currently running.", + }, []string{"template_name"}), + PrebuildJobsFailed: *prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "prebuild_jobs_failed", + Help: "Number of prebuild jobs that have failed.", + }, []string{"template_name"}), + PrebuildJobsCompleted: *prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "prebuild_jobs_completed", + Help: "Number of prebuild jobs that have completed successfully.", + }, []string{"template_name"}), + PrebuildDeletionJobsCreated: *prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "prebuild_deletion_jobs_created", + Help: "Number of prebuild deletion jobs that have been created.", + }, []string{"template_name"}), + PrebuildDeletionJobsRunning: *prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "prebuild_deletion_jobs_running", + Help: "Number of prebuild deletion jobs that are currently running.", + }, []string{"template_name"}), + PrebuildDeletionJobsFailed: *prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "prebuild_deletion_jobs_failed", + Help: "Number of prebuild deletion jobs that have failed.", + }, []string{"template_name"}), + PrebuildDeletionJobsCompleted: *prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "prebuild_deletion_jobs_completed", + Help: "Number of prebuild deletion jobs that have completed successfully.", + }, []string{"template_name"}), + PrebuildErrorsTotal: *prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "coderd", + Subsystem: "scaletest", + Name: "prebuild_errors_total", + Help: "Total number of prebuild errors", + }, []string{"template_name", "action"}), + } + + reg.MustRegister(m.PrebuildJobsCreated) + reg.MustRegister(m.PrebuildJobsRunning) + reg.MustRegister(m.PrebuildJobsFailed) + reg.MustRegister(m.PrebuildJobsCompleted) + reg.MustRegister(m.PrebuildDeletionJobsCreated) + reg.MustRegister(m.PrebuildDeletionJobsRunning) + reg.MustRegister(m.PrebuildDeletionJobsFailed) + reg.MustRegister(m.PrebuildDeletionJobsCompleted) + reg.MustRegister(m.PrebuildErrorsTotal) + return m +} + +func (m *Metrics) SetJobsCreated(count int, templateName string) { + m.PrebuildJobsCreated.WithLabelValues(templateName).Set(float64(count)) +} + +func (m *Metrics) SetJobsRunning(count int, templateName string) { + m.PrebuildJobsRunning.WithLabelValues(templateName).Set(float64(count)) +} + +func (m *Metrics) SetJobsFailed(count int, templateName string) { + m.PrebuildJobsFailed.WithLabelValues(templateName).Set(float64(count)) +} + +func (m *Metrics) SetJobsCompleted(count int, templateName string) { + m.PrebuildJobsCompleted.WithLabelValues(templateName).Set(float64(count)) +} + +func (m *Metrics) SetDeletionJobsCreated(count int, templateName string) { + m.PrebuildDeletionJobsCreated.WithLabelValues(templateName).Set(float64(count)) +} + +func (m *Metrics) SetDeletionJobsRunning(count int, templateName string) { + m.PrebuildDeletionJobsRunning.WithLabelValues(templateName).Set(float64(count)) +} + +func (m *Metrics) SetDeletionJobsFailed(count int, templateName string) { + m.PrebuildDeletionJobsFailed.WithLabelValues(templateName).Set(float64(count)) +} + +func (m *Metrics) SetDeletionJobsCompleted(count int, templateName string) { + m.PrebuildDeletionJobsCompleted.WithLabelValues(templateName).Set(float64(count)) +} + +func (m *Metrics) AddError(templateName string, action string) { + m.PrebuildErrorsTotal.WithLabelValues(templateName, action).Inc() +} diff --git a/scaletest/prebuilds/run.go b/scaletest/prebuilds/run.go new file mode 100644 index 0000000000000..7a62e3638bf8b --- /dev/null +++ b/scaletest/prebuilds/run.go @@ -0,0 +1,343 @@ +package prebuilds + +import ( + "bytes" + "context" + _ "embed" + "html/template" + "io" + "time" + + "golang.org/x/xerrors" + + "github.com/google/uuid" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" + "github.com/coder/coder/v2/coderd/tracing" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/scaletest/harness" + "github.com/coder/coder/v2/scaletest/loadtestutil" +) + +type Runner struct { + client *codersdk.Client + cfg Config + + template codersdk.Template +} + +var ( + _ harness.Runnable = &Runner{} + _ harness.Cleanable = &Runner{} +) + +func NewRunner(client *codersdk.Client, cfg Config) *Runner { + return &Runner{ + client: client, + cfg: cfg, + } +} + +func (r *Runner) Run(ctx context.Context, id string, logs io.Writer) error { + ctx, span := tracing.StartSpan(ctx) + defer span.End() + + reachedSetupBarrier := false + reachedCreationBarrier := false + reachedDeletionBarrier := false + defer func() { + if !reachedSetupBarrier { + r.cfg.SetupBarrier.Done() + } + if !reachedCreationBarrier { + r.cfg.CreationBarrier.Done() + } + if !reachedDeletionBarrier { + r.cfg.DeletionBarrier.Done() + } + }() + + logs = loadtestutil.NewSyncWriter(logs) + logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug) + r.client.SetLogger(logger) + r.client.SetLogBodies(true) + + templateName := "scaletest-prebuilds-template-" + id + + version, err := r.createTemplateVersion(ctx, uuid.Nil, r.cfg.NumPresets, r.cfg.NumPresetPrebuilds) + if err != nil { + r.cfg.Metrics.AddError(templateName, "create_template_version") + return err + } + + templateReq := codersdk.CreateTemplateRequest{ + Name: templateName, + Description: "`coder exp scaletest prebuilds` template", + VersionID: version.ID, + } + templ, err := r.client.CreateTemplate(ctx, r.cfg.OrganizationID, templateReq) + if err != nil { + r.cfg.Metrics.AddError(templateName, "create_template") + return xerrors.Errorf("create template: %w", err) + } + logger.Info(ctx, "created template", slog.F("template_id", templ.ID)) + + r.template = templ + + logger.Info(ctx, "waiting for all runners to reach setup barrier") + reachedSetupBarrier = true + r.cfg.SetupBarrier.Done() + r.cfg.SetupBarrier.Wait() + logger.Info(ctx, "all runners reached setup barrier, proceeding with prebuild creation test") + + err = r.measureCreation(ctx, logger) + if err != nil { + return err + } + + logger.Info(ctx, "waiting for all runners to reach creation barrier") + reachedCreationBarrier = true + r.cfg.CreationBarrier.Done() + r.cfg.CreationBarrier.Wait() + logger.Info(ctx, "all runners reached creation barrier") + + logger.Info(ctx, "waiting for runner owner to pause prebuilds (deletion setup barrier)") + r.cfg.DeletionSetupBarrier.Wait() + logger.Info(ctx, "prebuilds paused, preparing for deletion") + + // Now prepare for deletion by creating an empty template version + // At this point, prebuilds should be paused by the caller + logger.Info(ctx, "creating empty template version for deletion") + emptyVersion, err := r.createTemplateVersion(ctx, r.template.ID, 0, 0) + if err != nil { + r.cfg.Metrics.AddError(r.template.Name, "create_empty_template_version") + return xerrors.Errorf("create empty template version for deletion: %w", err) + } + + err = r.client.UpdateActiveTemplateVersion(ctx, r.template.ID, codersdk.UpdateActiveTemplateVersion{ + ID: emptyVersion.ID, + }) + if err != nil { + r.cfg.Metrics.AddError(r.template.Name, "update_active_template_version") + return xerrors.Errorf("update active template version to empty for deletion: %w", err) + } + + logger.Info(ctx, "waiting for all runners to reach deletion barrier") + reachedDeletionBarrier = true + r.cfg.DeletionBarrier.Done() + r.cfg.DeletionBarrier.Wait() + logger.Info(ctx, "all runners reached deletion barrier, proceeding with prebuild deletion test") + + err = r.measureDeletion(ctx, logger) + if err != nil { + return err + } + + return nil +} + +func (r *Runner) measureCreation(ctx context.Context, logger slog.Logger) error { + testStartTime := time.Now().UTC() + const workspacesPollInterval = 500 * time.Millisecond + + targetNumWorkspaces := r.cfg.NumPresets * r.cfg.NumPresetPrebuilds + + workspacesCtx, cancel := context.WithTimeout(ctx, r.cfg.PrebuildWorkspaceTimeout) + defer cancel() + + tkr := r.cfg.Clock.TickerFunc(workspacesCtx, workspacesPollInterval, func() error { + workspaces, err := r.client.Workspaces(workspacesCtx, codersdk.WorkspaceFilter{ + Template: r.template.Name, + }) + if err != nil { + return xerrors.Errorf("list workspaces: %w", err) + } + + createdCount := len(workspaces.Workspaces) + runningCount := 0 + failedCount := 0 + succeededCount := 0 + + for _, ws := range workspaces.Workspaces { + switch ws.LatestBuild.Job.Status { + case codersdk.ProvisionerJobRunning: + runningCount++ + case codersdk.ProvisionerJobFailed, codersdk.ProvisionerJobCanceled: + failedCount++ + case codersdk.ProvisionerJobSucceeded: + succeededCount++ + } + } + + r.cfg.Metrics.SetJobsCreated(createdCount, r.template.Name) + r.cfg.Metrics.SetJobsRunning(runningCount, r.template.Name) + r.cfg.Metrics.SetJobsFailed(failedCount, r.template.Name) + r.cfg.Metrics.SetJobsCompleted(succeededCount, r.template.Name) + + if succeededCount >= targetNumWorkspaces { + // All jobs succeeded + return errTickerDone + } + + return nil + }, "waitForPrebuildWorkspaces") + err := tkr.Wait() + if !xerrors.Is(err, errTickerDone) { + r.cfg.Metrics.AddError(r.template.Name, "wait_for_workspaces") + return xerrors.Errorf("wait for workspaces: %w", err) + } + + logger.Info(ctx, "all prebuild workspaces created successfully", slog.F("template_name", r.template.Name), slog.F("duration", time.Since(testStartTime).String())) + return nil +} + +func (r *Runner) measureDeletion(ctx context.Context, logger slog.Logger) error { + deletionStartTime := time.Now().UTC() + const deletionPollInterval = 500 * time.Millisecond + + targetNumWorkspaces := r.cfg.NumPresets * r.cfg.NumPresetPrebuilds + + deletionCtx, cancel := context.WithTimeout(ctx, r.cfg.PrebuildWorkspaceTimeout) + defer cancel() + + tkr := r.cfg.Clock.TickerFunc(deletionCtx, deletionPollInterval, func() error { + workspaces, err := r.client.Workspaces(deletionCtx, codersdk.WorkspaceFilter{ + Template: r.template.Name, + }) + if err != nil { + return xerrors.Errorf("list workspaces: %w", err) + } + + createdCount := 0 + runningCount := 0 + failedCount := 0 + + for _, ws := range workspaces.Workspaces { + if ws.LatestBuild.Transition == codersdk.WorkspaceTransitionDelete { + createdCount++ + switch ws.LatestBuild.Job.Status { + case codersdk.ProvisionerJobRunning: + runningCount++ + case codersdk.ProvisionerJobFailed, codersdk.ProvisionerJobCanceled: + failedCount++ + } + } + } + + completedCount := targetNumWorkspaces - len(workspaces.Workspaces) + createdCount += completedCount + + r.cfg.Metrics.SetDeletionJobsCreated(createdCount, r.template.Name) + r.cfg.Metrics.SetDeletionJobsRunning(runningCount, r.template.Name) + r.cfg.Metrics.SetDeletionJobsFailed(failedCount, r.template.Name) + r.cfg.Metrics.SetDeletionJobsCompleted(completedCount, r.template.Name) + + if len(workspaces.Workspaces) == 0 { + return errTickerDone + } + + return nil + }, "waitForPrebuildWorkspacesDeletion") + err := tkr.Wait() + if !xerrors.Is(err, errTickerDone) { + r.cfg.Metrics.AddError(r.template.Name, "wait_for_workspace_deletion") + return xerrors.Errorf("wait for workspace deletion: %w", err) + } + + logger.Info(ctx, "all prebuild workspaces deleted successfully", slog.F("template_name", r.template.Name), slog.F("duration", time.Since(deletionStartTime).String())) + return nil +} + +func (r *Runner) createTemplateVersion(ctx context.Context, templateID uuid.UUID, numPresets, numPresetPrebuilds int) (codersdk.TemplateVersion, error) { + tarData, err := TemplateTarData(numPresets, numPresetPrebuilds) + if err != nil { + return codersdk.TemplateVersion{}, xerrors.Errorf("create prebuilds template tar: %w", err) + } + uploadResp, err := r.client.Upload(ctx, codersdk.ContentTypeTar, bytes.NewReader(tarData)) + if err != nil { + return codersdk.TemplateVersion{}, xerrors.Errorf("upload prebuilds template tar: %w", err) + } + + versionReq := codersdk.CreateTemplateVersionRequest{ + TemplateID: templateID, + FileID: uploadResp.ID, + Message: "Template version for scaletest prebuilds", + StorageMethod: codersdk.ProvisionerStorageMethodFile, + Provisioner: codersdk.ProvisionerTypeTerraform, + } + version, err := r.client.CreateTemplateVersion(ctx, r.cfg.OrganizationID, versionReq) + if err != nil { + return codersdk.TemplateVersion{}, xerrors.Errorf("create template version: %w", err) + } + if version.MatchedProvisioners != nil && version.MatchedProvisioners.Count == 0 { + return codersdk.TemplateVersion{}, xerrors.Errorf("no provisioners matched for template version") + } + + const pollInterval = 2 * time.Second + versionCtx, cancel := context.WithTimeout(ctx, r.cfg.TemplateVersionJobTimeout) + defer cancel() + + tkr := r.cfg.Clock.TickerFunc(versionCtx, pollInterval, func() error { + version, err := r.client.TemplateVersion(versionCtx, version.ID) + if err != nil { + return xerrors.Errorf("get template version: %w", err) + } + switch version.Job.Status { + case codersdk.ProvisionerJobSucceeded: + return errTickerDone + case codersdk.ProvisionerJobPending, codersdk.ProvisionerJobRunning: + return nil + default: + return xerrors.Errorf("template version provisioning failed: status %s", version.Job.Status) + } + }) + err = tkr.Wait() + if !xerrors.Is(err, errTickerDone) { + return codersdk.TemplateVersion{}, xerrors.Errorf("wait for template version provisioning: %w", err) + } + return version, nil +} + +var errTickerDone = xerrors.New("done") + +func (r *Runner) Cleanup(ctx context.Context, _ string, logs io.Writer) error { + logs = loadtestutil.NewSyncWriter(logs) + logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug) + + logger.Info(ctx, "deleting template", slog.F("template_name", r.template.Name)) + + err := r.client.DeleteTemplate(ctx, r.template.ID) + if err != nil { + return xerrors.Errorf("delete template: %w", err) + } + + logger.Info(ctx, "template deleted successfully", slog.F("template_name", r.template.Name)) + return nil +} + +//go:embed tf/main.tf.tpl +var templateContent string + +func TemplateTarData(numPresets, numPresetPrebuilds int) ([]byte, error) { + tmpl, err := template.New("prebuilds-template").Parse(templateContent) + if err != nil { + return nil, err + } + result := bytes.Buffer{} + err = tmpl.Execute(&result, map[string]int{ + "NumPresets": numPresets, + "NumPresetPrebuilds": numPresetPrebuilds, + }) + if err != nil { + return nil, err + } + files := map[string][]byte{ + "main.tf": result.Bytes(), + } + tarBytes, err := loadtestutil.CreateTarFromFiles(files) + if err != nil { + return nil, err + } + return tarBytes, nil +} diff --git a/scaletest/prebuilds/tf/main.tf.tpl b/scaletest/prebuilds/tf/main.tf.tpl new file mode 100644 index 0000000000000..9465281ac2ba9 --- /dev/null +++ b/scaletest/prebuilds/tf/main.tf.tpl @@ -0,0 +1,18 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "2.5.3" + } + } +} + +resource "null_resource" "workspace" {} + +data "coder_workspace_preset" "presets" { + count = {{.NumPresets}} + name = "preset-${count.index + 1}" + prebuilds { + instances = {{.NumPresetPrebuilds}} + } +} From 6bafbb7bc5cc60541af72b3ccee0d48e8adbaf33 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:08:14 +1100 Subject: [PATCH 144/255] feat(cli): add `prebuilds` scaletest command (#20600) Closes https://github.com/coder/internal/issues/914 --- cli/exp_scaletest.go | 1 + cli/exp_scaletest_prebuilds.go | 297 +++++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 cli/exp_scaletest_prebuilds.go diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index d70780e4c3e0c..f6fd350da23da 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -65,6 +65,7 @@ func (r *RootCmd) scaletestCmd() *serpent.Command { r.scaletestAutostart(), r.scaletestNotifications(), r.scaletestSMTP(), + r.scaletestPrebuilds(), }, } diff --git a/cli/exp_scaletest_prebuilds.go b/cli/exp_scaletest_prebuilds.go new file mode 100644 index 0000000000000..8e05bacae258d --- /dev/null +++ b/cli/exp_scaletest_prebuilds.go @@ -0,0 +1,297 @@ +//go:build !slim + +package cli + +import ( + "fmt" + "net/http" + "os/signal" + "strconv" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/scaletest/harness" + "github.com/coder/coder/v2/scaletest/prebuilds" + "github.com/coder/quartz" + "github.com/coder/serpent" +) + +func (r *RootCmd) scaletestPrebuilds() *serpent.Command { + var ( + numTemplates int64 + numPresets int64 + numPresetPrebuilds int64 + templateVersionJobTimeout time.Duration + prebuildWorkspaceTimeout time.Duration + noCleanup bool + + tracingFlags = &scaletestTracingFlags{} + timeoutStrategy = &timeoutFlags{} + cleanupStrategy = newScaletestCleanupStrategy() + output = &scaletestOutputFlags{} + prometheusFlags = &scaletestPrometheusFlags{} + ) + + cmd := &serpent.Command{ + Use: "prebuilds", + Short: "Creates prebuild workspaces on the Coder server.", + Handler: func(inv *serpent.Invocation) error { + ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } + + notifyCtx, stop := signal.NotifyContext(ctx, StopSignals...) + defer stop() + ctx = notifyCtx + + me, err := requireAdmin(ctx, client) + if err != nil { + return err + } + + client.HTTPClient = &http.Client{ + Transport: &codersdk.HeaderTransport{ + Transport: http.DefaultTransport, + Header: map[string][]string{ + codersdk.BypassRatelimitHeader: {"true"}, + }, + }, + } + + if numTemplates <= 0 { + return xerrors.Errorf("--num-templates must be greater than 0") + } + if numPresets <= 0 { + return xerrors.Errorf("--num-presets must be greater than 0") + } + if numPresetPrebuilds <= 0 { + return xerrors.Errorf("--num-preset-prebuilds must be greater than 0") + } + + outputs, err := output.parse() + if err != nil { + return xerrors.Errorf("parse output flags: %w", err) + } + + tracerProvider, closeTracing, tracingEnabled, err := tracingFlags.provider(ctx) + if err != nil { + return xerrors.Errorf("create tracer provider: %w", err) + } + defer func() { + _, _ = fmt.Fprintln(inv.Stderr, "\nUploading traces...") + if err := closeTracing(ctx); err != nil { + _, _ = fmt.Fprintf(inv.Stderr, "\nError uploading traces: %+v\n", err) + } + _, _ = fmt.Fprintf(inv.Stderr, "Waiting %s for prometheus metrics to be scraped\n", prometheusFlags.Wait) + <-time.After(prometheusFlags.Wait) + }() + tracer := tracerProvider.Tracer(scaletestTracerName) + + reg := prometheus.NewRegistry() + metrics := prebuilds.NewMetrics(reg) + + logger := inv.Logger + prometheusSrvClose := ServeHandler(ctx, logger, promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), prometheusFlags.Address, "prometheus") + defer prometheusSrvClose() + + err = client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{ + ReconciliationPaused: true, + }) + if err != nil { + return xerrors.Errorf("pause prebuilds: %w", err) + } + + setupBarrier := new(sync.WaitGroup) + setupBarrier.Add(int(numTemplates)) + creationBarrier := new(sync.WaitGroup) + creationBarrier.Add(int(numTemplates)) + deletionSetupBarrier := new(sync.WaitGroup) + deletionSetupBarrier.Add(1) + deletionBarrier := new(sync.WaitGroup) + deletionBarrier.Add(int(numTemplates)) + + th := harness.NewTestHarness(timeoutStrategy.wrapStrategy(harness.ConcurrentExecutionStrategy{}), cleanupStrategy.toStrategy()) + + for i := range numTemplates { + id := strconv.Itoa(int(i)) + cfg := prebuilds.Config{ + OrganizationID: me.OrganizationIDs[0], + NumPresets: int(numPresets), + NumPresetPrebuilds: int(numPresetPrebuilds), + TemplateVersionJobTimeout: templateVersionJobTimeout, + PrebuildWorkspaceTimeout: prebuildWorkspaceTimeout, + Metrics: metrics, + SetupBarrier: setupBarrier, + CreationBarrier: creationBarrier, + DeletionSetupBarrier: deletionSetupBarrier, + DeletionBarrier: deletionBarrier, + Clock: quartz.NewReal(), + } + err := cfg.Validate() + if err != nil { + return xerrors.Errorf("validate config: %w", err) + } + + var runner harness.Runnable = prebuilds.NewRunner(client, cfg) + if tracingEnabled { + runner = &runnableTraceWrapper{ + tracer: tracer, + spanName: fmt.Sprintf("prebuilds/%s", id), + runner: runner, + } + } + + th.AddRun("prebuilds", id, runner) + } + + _, _ = fmt.Fprintf(inv.Stderr, "Creating %d templates with %d presets and %d prebuilds per preset...\n", + numTemplates, numPresets, numPresetPrebuilds) + _, _ = fmt.Fprintf(inv.Stderr, "Total expected prebuilds: %d\n", numTemplates*numPresets*numPresetPrebuilds) + + testCtx, testCancel := timeoutStrategy.toContext(ctx) + defer testCancel() + + runErrCh := make(chan error, 1) + go func() { + runErrCh <- th.Run(testCtx) + }() + + _, _ = fmt.Fprintln(inv.Stderr, "Waiting for all templates to be created...") + setupBarrier.Wait() + _, _ = fmt.Fprintln(inv.Stderr, "All templates created") + + err = client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{ + ReconciliationPaused: false, + }) + if err != nil { + return xerrors.Errorf("resume prebuilds: %w", err) + } + + _, _ = fmt.Fprintln(inv.Stderr, "Waiting for all prebuilds to be created...") + creationBarrier.Wait() + _, _ = fmt.Fprintln(inv.Stderr, "All prebuilds created") + + err = client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{ + ReconciliationPaused: true, + }) + if err != nil { + return xerrors.Errorf("pause prebuilds before deletion: %w", err) + } + + _, _ = fmt.Fprintln(inv.Stderr, "Prebuilds paused, signaling runners to prepare for deletion") + deletionSetupBarrier.Done() + + _, _ = fmt.Fprintln(inv.Stderr, "Waiting for all templates to be updated with 0 prebuilds...") + deletionBarrier.Wait() + _, _ = fmt.Fprintln(inv.Stderr, "All templates updated") + + err = client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{ + ReconciliationPaused: false, + }) + if err != nil { + return xerrors.Errorf("resume prebuilds for deletion: %w", err) + } + + _, _ = fmt.Fprintln(inv.Stderr, "Waiting for all prebuilds to be deleted...") + err = <-runErrCh + if err != nil { + return xerrors.Errorf("run test harness (harness failure, not a test failure): %w", err) + } + + // If the command was interrupted, skip cleanup & stats + if notifyCtx.Err() != nil { + return notifyCtx.Err() + } + + res := th.Results() + for _, o := range outputs { + err = o.write(res, inv.Stdout) + if err != nil { + return xerrors.Errorf("write output %q to %q: %w", o.format, o.path, err) + } + } + + if !noCleanup { + _, _ = fmt.Fprintln(inv.Stderr, "\nStarting cleanup (deleting templates)...") + + cleanupCtx, cleanupCancel := cleanupStrategy.toContext(ctx) + defer cleanupCancel() + + err = th.Cleanup(cleanupCtx) + if err != nil { + return xerrors.Errorf("cleanup tests: %w", err) + } + + // If the cleanup was interrupted, skip stats + if notifyCtx.Err() != nil { + return notifyCtx.Err() + } + } + + if res.TotalFail > 0 { + return xerrors.New("prebuild creation test failed, see above for more details") + } + + return nil + }, + } + + cmd.Options = serpent.OptionSet{ + { + Flag: "num-templates", + Env: "CODER_SCALETEST_PREBUILDS_NUM_TEMPLATES", + Default: "1", + Description: "Number of templates to create for the test.", + Value: serpent.Int64Of(&numTemplates), + }, + { + Flag: "num-presets", + Env: "CODER_SCALETEST_PREBUILDS_NUM_PRESETS", + Default: "1", + Description: "Number of presets per template.", + Value: serpent.Int64Of(&numPresets), + }, + { + Flag: "num-preset-prebuilds", + Env: "CODER_SCALETEST_PREBUILDS_NUM_PRESET_PREBUILDS", + Default: "1", + Description: "Number of prebuilds per preset.", + Value: serpent.Int64Of(&numPresetPrebuilds), + }, + { + Flag: "template-version-job-timeout", + Env: "CODER_SCALETEST_PREBUILDS_TEMPLATE_VERSION_JOB_TIMEOUT", + Default: "5m", + Description: "Timeout for template version provisioning jobs.", + Value: serpent.DurationOf(&templateVersionJobTimeout), + }, + { + Flag: "prebuild-workspace-timeout", + Env: "CODER_SCALETEST_PREBUILDS_WORKSPACE_TIMEOUT", + Default: "10m", + Description: "Timeout for all prebuild workspaces to be created/deleted.", + Value: serpent.DurationOf(&prebuildWorkspaceTimeout), + }, + { + Flag: "skip-cleanup", + Env: "CODER_SCALETEST_PREBUILDS_SKIP_CLEANUP", + Description: "Skip cleanup (deletion test) and leave resources intact.", + Value: serpent.BoolOf(&noCleanup), + }, + } + + tracingFlags.attach(&cmd.Options) + timeoutStrategy.attach(&cmd.Options) + cleanupStrategy.attach(&cmd.Options) + output.attach(&cmd.Options) + prometheusFlags.attach(&cmd.Options) + + return cmd +} From 86c49484453cbe9bf3270b3d77241c13d74c751d Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Fri, 14 Nov 2025 12:10:53 +0200 Subject: [PATCH 145/255] chore: add timing flag context to warn message (#20772) `prometheus.provisionerd_server_metrics: unsupported workspace timing flags` appears in the logs, but without knowledge of the available flags it's not possible to troubleshoot this. Signed-off-by: Danny Kopping --- coderd/provisionerdserver/metrics.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/coderd/provisionerdserver/metrics.go b/coderd/provisionerdserver/metrics.go index b1afc10670f22..442214f3c63ab 100644 --- a/coderd/provisionerdserver/metrics.go +++ b/coderd/provisionerdserver/metrics.go @@ -153,6 +153,9 @@ func (m *Metrics) UpdateWorkspaceTimingsMetrics( m.workspaceClaimTimings. WithLabelValues(organizationName, templateName, presetName).Observe(buildTime) default: - m.logger.Warn(ctx, "unsupported workspace timing flags") + m.logger.Warn(ctx, "unsupported workspace timing flags", + "isPrebuild", flags.IsPrebuild, + "isClaim", flags.IsClaim, + "isWorkspaceFirstBuild", flags.IsFirstBuild) } } From 79d46769fe448dc324a42ba4f56c805c88e5fae4 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Fri, 14 Nov 2025 12:26:32 +0000 Subject: [PATCH 146/255] chore: remove warning for non-trackable workspace builds in metrics (#20775) Previously, `UpdateWorkspaceTimingsMetrics` would log a warning for workspace builds that aren't tracked (restarts, stops, subsequent builds after creation). This was noisy since these are legitimate operations, not errors. `UpdateWorkspaceTimingsMetrics` is specifically designed to track only workspace creation, prebuild creation, and prebuild claim timings. Related with: https://github.com/coder/coder/pull/20772 --- coderd/provisionerdserver/metrics.go | 11 ++-- .../provisionerdserver/provisionerdserver.go | 60 ++++++++++--------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/coderd/provisionerdserver/metrics.go b/coderd/provisionerdserver/metrics.go index 442214f3c63ab..204bc2e717402 100644 --- a/coderd/provisionerdserver/metrics.go +++ b/coderd/provisionerdserver/metrics.go @@ -100,6 +100,12 @@ func (m *Metrics) Register(reg prometheus.Registerer) error { return reg.Register(m.workspaceClaimTimings) } +// IsTrackable returns true if the workspace build should be tracked in metrics. +// This includes workspace creation, prebuild creation, and prebuild claims. +func (f WorkspaceTimingFlags) IsTrackable() bool { + return f.IsPrebuild || f.IsClaim || f.IsFirstBuild +} + // getWorkspaceTimingType classifies a workspace build: // - PrebuildCreation: creation of a prebuilt workspace // - PrebuildClaim: claim of an existing prebuilt workspace @@ -153,9 +159,6 @@ func (m *Metrics) UpdateWorkspaceTimingsMetrics( m.workspaceClaimTimings. WithLabelValues(organizationName, templateName, presetName).Observe(buildTime) default: - m.logger.Warn(ctx, "unsupported workspace timing flags", - "isPrebuild", flags.IsPrebuild, - "isClaim", flags.IsClaim, - "isWorkspaceFirstBuild", flags.IsFirstBuild) + // Not a trackable build type (e.g. restart, stop, subsequent builds) } } diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 5ea01afcac829..56670d8a2d9e8 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -2336,40 +2336,42 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro } // Update workspace (regular and prebuild) timing metrics - if s.metrics != nil { - // Only consider 'start' workspace builds - if workspaceBuild.Transition == database.WorkspaceTransitionStart { - // Get the updated job to report the metrics with correct data - updatedJob, err := s.Database.GetProvisionerJobByID(ctx, jobID) - if err != nil { - s.Logger.Error(ctx, "get updated job from database", slog.Error(err)) - } else - // Only consider 'succeeded' provisioner jobs - if updatedJob.JobStatus == database.ProvisionerJobStatusSucceeded { - presetName := "" - if workspaceBuild.TemplateVersionPresetID.Valid { - preset, err := s.Database.GetPresetByID(ctx, workspaceBuild.TemplateVersionPresetID.UUID) - if err != nil { - if !errors.Is(err, sql.ErrNoRows) { - s.Logger.Error(ctx, "get preset by ID for workspace timing metrics", slog.Error(err)) - } - } else { - presetName = preset.Name + // Only consider 'start' workspace builds + if s.metrics != nil && workspaceBuild.Transition == database.WorkspaceTransitionStart { + // Get the updated job to report the metrics with correct data + updatedJob, err := s.Database.GetProvisionerJobByID(ctx, jobID) + if err != nil { + s.Logger.Error(ctx, "get updated job from database", slog.Error(err)) + } else + // Only consider 'succeeded' provisioner jobs + if updatedJob.JobStatus == database.ProvisionerJobStatusSucceeded { + presetName := "" + if workspaceBuild.TemplateVersionPresetID.Valid { + preset, err := s.Database.GetPresetByID(ctx, workspaceBuild.TemplateVersionPresetID.UUID) + if err != nil { + if !errors.Is(err, sql.ErrNoRows) { + s.Logger.Error(ctx, "get preset by ID for workspace timing metrics", slog.Error(err)) } + } else { + presetName = preset.Name } + } - buildTime := updatedJob.CompletedAt.Time.Sub(updatedJob.StartedAt.Time).Seconds() + buildTime := updatedJob.CompletedAt.Time.Sub(updatedJob.StartedAt.Time).Seconds() + flags := WorkspaceTimingFlags{ + // Is a prebuilt workspace creation build + IsPrebuild: input.PrebuiltWorkspaceBuildStage.IsPrebuild(), + // Is a prebuilt workspace claim build + IsClaim: input.PrebuiltWorkspaceBuildStage.IsPrebuiltWorkspaceClaim(), + // Is a regular workspace creation build + // Only consider the first build number for regular workspaces + IsFirstBuild: workspaceBuild.BuildNumber == 1, + } + // Only track metrics for prebuild creation, prebuild claims and workspace creation + if flags.IsTrackable() { s.metrics.UpdateWorkspaceTimingsMetrics( ctx, - WorkspaceTimingFlags{ - // Is a prebuilt workspace creation build - IsPrebuild: input.PrebuiltWorkspaceBuildStage.IsPrebuild(), - // Is a prebuilt workspace claim build - IsClaim: input.PrebuiltWorkspaceBuildStage.IsPrebuiltWorkspaceClaim(), - // Is a regular workspace creation build - // Only consider the first build number for regular workspaces - IsFirstBuild: workspaceBuild.BuildNumber == 1, - }, + flags, workspace.OrganizationName, workspace.TemplateName, presetName, From da9214e2124bc26953fb07e8e2d298e5edaab19f Mon Sep 17 00:00:00 2001 From: 35C4n0r <70096901+35C4n0r@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:37:20 +0530 Subject: [PATCH 147/255] chore: add opencode icon (#20670) Co-authored-by: 35C4n0r --- site/src/theme/icons.json | 1 + site/static/icon/opencode.svg | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 site/static/icon/opencode.svg diff --git a/site/src/theme/icons.json b/site/src/theme/icons.json index 53dd9f6e9fb92..408ba3e3c749b 100644 --- a/site/src/theme/icons.json +++ b/site/src/theme/icons.json @@ -92,6 +92,7 @@ "novnc.svg", "okta.svg", "openai.svg", + "opencode.svg", "personalize.svg", "php.svg", "phpstorm.svg", diff --git a/site/static/icon/opencode.svg b/site/static/icon/opencode.svg new file mode 100644 index 0000000000000..0c696a0577a53 --- /dev/null +++ b/site/static/icon/opencode.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + From f23836d42662d739f5305ae5d20adbeb416a91f0 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 14 Nov 2025 08:30:10 -0600 Subject: [PATCH 148/255] chore: add more scopes to the curated catalog (#20746) Just noticed when writing docs. These are probably obvious scopes to allow --- coderd/rbac/scopes_catalog.go | 7 +++++++ codersdk/apikey_scopes_gen.go | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/coderd/rbac/scopes_catalog.go b/coderd/rbac/scopes_catalog.go index ef4f3186de4fd..7f6b538bd5bfd 100644 --- a/coderd/rbac/scopes_catalog.go +++ b/coderd/rbac/scopes_catalog.go @@ -43,6 +43,7 @@ var externalLowLevel = map[ScopeName]struct{}{ // Users (personal profile only) "user:read_personal": {}, "user:update_personal": {}, + "user.*": {}, // User secrets "user_secret:read": {}, @@ -57,6 +58,12 @@ var externalLowLevel = map[ScopeName]struct{}{ "task:update": {}, "task:delete": {}, "task:*": {}, + + // Organizations + "organization:read": {}, + "organization:update": {}, + "organization:delete": {}, + "organization:*": {}, } // Public composite coder:* scopes exposed to users. diff --git a/codersdk/apikey_scopes_gen.go b/codersdk/apikey_scopes_gen.go index df7fe96c4585e..f4bc90152dd42 100644 --- a/codersdk/apikey_scopes_gen.go +++ b/codersdk/apikey_scopes_gen.go @@ -221,6 +221,10 @@ var PublicAPIKeyScopes = []APIKeyScope{ APIKeyScopeFileAll, APIKeyScopeFileCreate, APIKeyScopeFileRead, + APIKeyScopeOrganizationAll, + APIKeyScopeOrganizationDelete, + APIKeyScopeOrganizationRead, + APIKeyScopeOrganizationUpdate, APIKeyScopeTaskAll, APIKeyScopeTaskCreate, APIKeyScopeTaskDelete, From 6067aa3aa1553495cca2e4516ca5c1c294eb203b Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Fri, 14 Nov 2025 17:09:06 +0000 Subject: [PATCH 149/255] fix: correctly set touched for autofill dynamic parameters (#20769) this resolves #20187 This bug was introduced in PR https://github.com/coder/coder/pull/18894 in CreateWorkspacePageViewExperimental.tsx This PR made a change so that the entire parameter was used instead of just the parameter name to mark an autofilled parameter as touched. The fix here is to just use the parameter name. --- .../CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx index cf1fd1746ce44..315c8f357db29 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx @@ -119,7 +119,7 @@ export const CreateWorkspacePageViewExperimental: FC< // Only touched fields are sent to the websocket // Autofilled parameters are marked as touched since they have been modified const initialTouched = Object.fromEntries( - parameters.filter((p) => autofillByName[p.name]).map((p) => [p, true]), + parameters.filter((p) => autofillByName[p.name]).map((p) => [p.name, true]), ); // The form parameters values hold the working state of the parameters that will be submitted when creating a workspace From 1483fd11fff9d0cf23ffb61a1871fbc5e461984c Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 14 Nov 2025 19:52:26 +0200 Subject: [PATCH 150/255] fix(coderd/database): improve task status in tasks_with_status view (#20683) This change restructures the `tasks_with_status` view query to: - Improve debuggability by adding a `status_debug` column to better understand the outcome - Reduce clutter from `bool_or`, `bool_and` which are aggregate functions that did not actually have serve a purpose (each join is 0-1 rows) - Improve agent lifecycle state coverage, `start_timeout` and `start_error` were omitted - These states are easy to trigger even in a perfectly functioning workspace/task so we now rely on app health to report whether or not there was an issue - Mark canceling and canceled workspace build jobs as error state - Agent stop states were implicitly `unknown`, now there are explicit (I initially considered `error`, could go either way) --- coderd/database/dump.sql | 64 ++++---- .../000397_update_task_status_view.down.sql | 82 ++++++++++ .../000397_update_task_status_view.up.sql | 142 ++++++++++++++++++ coderd/database/models.go | 37 ++--- coderd/database/querier_test.go | 55 ++++++- coderd/database/queries.sql.go | 20 ++- coderd/database/sqlc.yaml | 9 ++ 7 files changed, 354 insertions(+), 55 deletions(-) create mode 100644 coderd/database/migrations/000397_update_task_status_view.down.sql create mode 100644 coderd/database/migrations/000397_update_task_status_view.up.sql diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index c2e4091e56c96..e7b46c2f3445c 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1965,32 +1965,21 @@ CREATE VIEW tasks_with_status AS tasks.created_at, tasks.deleted_at, CASE - WHEN ((tasks.workspace_id IS NULL) OR (latest_build.job_status IS NULL)) THEN 'pending'::task_status - WHEN (latest_build.job_status = 'failed'::provisioner_job_status) THEN 'error'::task_status - WHEN ((latest_build.transition = ANY (ARRAY['stop'::workspace_transition, 'delete'::workspace_transition])) AND (latest_build.job_status = 'succeeded'::provisioner_job_status)) THEN 'paused'::task_status - WHEN ((latest_build.transition = 'start'::workspace_transition) AND (latest_build.job_status = 'pending'::provisioner_job_status)) THEN 'initializing'::task_status - WHEN ((latest_build.transition = 'start'::workspace_transition) AND (latest_build.job_status = ANY (ARRAY['running'::provisioner_job_status, 'succeeded'::provisioner_job_status]))) THEN - CASE - WHEN agent_status."none" THEN 'initializing'::task_status - WHEN agent_status.connecting THEN 'initializing'::task_status - WHEN agent_status.connected THEN - CASE - WHEN app_status.any_unhealthy THEN 'error'::task_status - WHEN app_status.any_initializing THEN 'initializing'::task_status - WHEN app_status.all_healthy_or_disabled THEN 'active'::task_status - ELSE 'unknown'::task_status - END - ELSE 'unknown'::task_status - END - ELSE 'unknown'::task_status + WHEN (tasks.workspace_id IS NULL) THEN 'pending'::task_status + WHEN (build_status.status <> 'active'::task_status) THEN build_status.status + WHEN (agent_status.status <> 'active'::task_status) THEN agent_status.status + ELSE app_status.status END AS status, + jsonb_build_object('build', jsonb_build_object('transition', latest_build_raw.transition, 'job_status', latest_build_raw.job_status, 'computed', build_status.status), 'agent', jsonb_build_object('lifecycle_state', agent_raw.lifecycle_state, 'computed', agent_status.status), 'app', jsonb_build_object('health', app_raw.health, 'computed', app_status.status)) AS status_debug, task_app.workspace_build_number, task_app.workspace_agent_id, task_app.workspace_app_id, + agent_raw.lifecycle_state AS workspace_agent_lifecycle_state, + app_raw.health AS workspace_app_health, task_owner.owner_username, task_owner.owner_name, task_owner.owner_avatar_url - FROM (((((tasks + FROM ((((((((tasks CROSS JOIN LATERAL ( SELECT vu.username AS owner_username, vu.name AS owner_name, vu.avatar_url AS owner_avatar_url @@ -2008,17 +1997,36 @@ CREATE VIEW tasks_with_status AS workspace_build.job_id FROM (workspace_builds workspace_build JOIN provisioner_jobs provisioner_job ON ((provisioner_job.id = workspace_build.job_id))) - WHERE ((workspace_build.workspace_id = tasks.workspace_id) AND (workspace_build.build_number = task_app.workspace_build_number))) latest_build ON (true)) - CROSS JOIN LATERAL ( SELECT (count(*) = 0) AS "none", - bool_or((workspace_agent.lifecycle_state = ANY (ARRAY['created'::workspace_agent_lifecycle_state, 'starting'::workspace_agent_lifecycle_state]))) AS connecting, - bool_and((workspace_agent.lifecycle_state = 'ready'::workspace_agent_lifecycle_state)) AS connected + WHERE ((workspace_build.workspace_id = tasks.workspace_id) AND (workspace_build.build_number = task_app.workspace_build_number))) latest_build_raw ON (true)) + LEFT JOIN LATERAL ( SELECT workspace_agent.lifecycle_state FROM workspace_agents workspace_agent - WHERE (workspace_agent.id = task_app.workspace_agent_id)) agent_status) - CROSS JOIN LATERAL ( SELECT bool_or((workspace_app.health = 'unhealthy'::workspace_app_health)) AS any_unhealthy, - bool_or((workspace_app.health = 'initializing'::workspace_app_health)) AS any_initializing, - bool_and((workspace_app.health = ANY (ARRAY['healthy'::workspace_app_health, 'disabled'::workspace_app_health]))) AS all_healthy_or_disabled + WHERE (workspace_agent.id = task_app.workspace_agent_id)) agent_raw ON (true)) + LEFT JOIN LATERAL ( SELECT workspace_app.health FROM workspace_apps workspace_app - WHERE (workspace_app.id = task_app.workspace_app_id)) app_status) + WHERE (workspace_app.id = task_app.workspace_app_id)) app_raw ON (true)) + CROSS JOIN LATERAL ( SELECT + CASE + WHEN (latest_build_raw.job_status IS NULL) THEN 'pending'::task_status + WHEN (latest_build_raw.job_status = ANY (ARRAY['failed'::provisioner_job_status, 'canceling'::provisioner_job_status, 'canceled'::provisioner_job_status])) THEN 'error'::task_status + WHEN ((latest_build_raw.transition = ANY (ARRAY['stop'::workspace_transition, 'delete'::workspace_transition])) AND (latest_build_raw.job_status = 'succeeded'::provisioner_job_status)) THEN 'paused'::task_status + WHEN ((latest_build_raw.transition = 'start'::workspace_transition) AND (latest_build_raw.job_status = 'pending'::provisioner_job_status)) THEN 'initializing'::task_status + WHEN ((latest_build_raw.transition = 'start'::workspace_transition) AND (latest_build_raw.job_status = ANY (ARRAY['running'::provisioner_job_status, 'succeeded'::provisioner_job_status]))) THEN 'active'::task_status + ELSE 'unknown'::task_status + END AS status) build_status) + CROSS JOIN LATERAL ( SELECT + CASE + WHEN ((agent_raw.lifecycle_state IS NULL) OR (agent_raw.lifecycle_state = ANY (ARRAY['created'::workspace_agent_lifecycle_state, 'starting'::workspace_agent_lifecycle_state]))) THEN 'initializing'::task_status + WHEN (agent_raw.lifecycle_state = ANY (ARRAY['ready'::workspace_agent_lifecycle_state, 'start_timeout'::workspace_agent_lifecycle_state, 'start_error'::workspace_agent_lifecycle_state])) THEN 'active'::task_status + WHEN (agent_raw.lifecycle_state <> ALL (ARRAY['created'::workspace_agent_lifecycle_state, 'starting'::workspace_agent_lifecycle_state, 'ready'::workspace_agent_lifecycle_state, 'start_timeout'::workspace_agent_lifecycle_state, 'start_error'::workspace_agent_lifecycle_state])) THEN 'unknown'::task_status + ELSE 'unknown'::task_status + END AS status) agent_status) + CROSS JOIN LATERAL ( SELECT + CASE + WHEN (app_raw.health = 'initializing'::workspace_app_health) THEN 'initializing'::task_status + WHEN (app_raw.health = 'unhealthy'::workspace_app_health) THEN 'error'::task_status + WHEN (app_raw.health = ANY (ARRAY['healthy'::workspace_app_health, 'disabled'::workspace_app_health])) THEN 'active'::task_status + ELSE 'unknown'::task_status + END AS status) app_status) WHERE (tasks.deleted_at IS NULL); CREATE TABLE telemetry_items ( diff --git a/coderd/database/migrations/000397_update_task_status_view.down.sql b/coderd/database/migrations/000397_update_task_status_view.down.sql new file mode 100644 index 0000000000000..a9380ec962b9a --- /dev/null +++ b/coderd/database/migrations/000397_update_task_status_view.down.sql @@ -0,0 +1,82 @@ +-- Restore previous view. +DROP VIEW IF EXISTS tasks_with_status; + +CREATE VIEW + tasks_with_status +AS + SELECT + tasks.*, + CASE + WHEN tasks.workspace_id IS NULL OR latest_build.job_status IS NULL THEN 'pending'::task_status + + WHEN latest_build.job_status = 'failed' THEN 'error'::task_status + + WHEN latest_build.transition IN ('stop', 'delete') + AND latest_build.job_status = 'succeeded' THEN 'paused'::task_status + + WHEN latest_build.transition = 'start' + AND latest_build.job_status = 'pending' THEN 'initializing'::task_status + + WHEN latest_build.transition = 'start' AND latest_build.job_status IN ('running', 'succeeded') THEN + CASE + WHEN agent_status.none THEN 'initializing'::task_status + WHEN agent_status.connecting THEN 'initializing'::task_status + WHEN agent_status.connected THEN + CASE + WHEN app_status.any_unhealthy THEN 'error'::task_status + WHEN app_status.any_initializing THEN 'initializing'::task_status + WHEN app_status.all_healthy_or_disabled THEN 'active'::task_status + ELSE 'unknown'::task_status + END + ELSE 'unknown'::task_status + END + + ELSE 'unknown'::task_status + END AS status, + task_app.*, + task_owner.* + FROM + tasks + CROSS JOIN LATERAL ( + SELECT + vu.username AS owner_username, + vu.name AS owner_name, + vu.avatar_url AS owner_avatar_url + FROM visible_users vu + WHERE vu.id = tasks.owner_id + ) task_owner + LEFT JOIN LATERAL ( + SELECT workspace_build_number, workspace_agent_id, workspace_app_id + FROM task_workspace_apps task_app + WHERE task_id = tasks.id + ORDER BY workspace_build_number DESC + LIMIT 1 + ) task_app ON TRUE + LEFT JOIN LATERAL ( + SELECT + workspace_build.transition, + provisioner_job.job_status, + workspace_build.job_id + FROM workspace_builds workspace_build + JOIN provisioner_jobs provisioner_job ON provisioner_job.id = workspace_build.job_id + WHERE workspace_build.workspace_id = tasks.workspace_id + AND workspace_build.build_number = task_app.workspace_build_number + ) latest_build ON TRUE + CROSS JOIN LATERAL ( + SELECT + COUNT(*) = 0 AS none, + bool_or(workspace_agent.lifecycle_state IN ('created', 'starting')) AS connecting, + bool_and(workspace_agent.lifecycle_state = 'ready') AS connected + FROM workspace_agents workspace_agent + WHERE workspace_agent.id = task_app.workspace_agent_id + ) agent_status + CROSS JOIN LATERAL ( + SELECT + bool_or(workspace_app.health = 'unhealthy') AS any_unhealthy, + bool_or(workspace_app.health = 'initializing') AS any_initializing, + bool_and(workspace_app.health IN ('healthy', 'disabled')) AS all_healthy_or_disabled + FROM workspace_apps workspace_app + WHERE workspace_app.id = task_app.workspace_app_id + ) app_status + WHERE + tasks.deleted_at IS NULL; diff --git a/coderd/database/migrations/000397_update_task_status_view.up.sql b/coderd/database/migrations/000397_update_task_status_view.up.sql new file mode 100644 index 0000000000000..f05df3c5b82ed --- /dev/null +++ b/coderd/database/migrations/000397_update_task_status_view.up.sql @@ -0,0 +1,142 @@ +-- Update task status in view. +DROP VIEW IF EXISTS tasks_with_status; + +CREATE VIEW + tasks_with_status +AS + SELECT + tasks.*, + -- Combine component statuses with precedence: build -> agent -> app. + CASE + WHEN tasks.workspace_id IS NULL THEN 'pending'::task_status + WHEN build_status.status != 'active' THEN build_status.status::task_status + WHEN agent_status.status != 'active' THEN agent_status.status::task_status + ELSE app_status.status::task_status + END AS status, + -- Attach debug information for troubleshooting status. + jsonb_build_object( + 'build', jsonb_build_object( + 'transition', latest_build_raw.transition, + 'job_status', latest_build_raw.job_status, + 'computed', build_status.status + ), + 'agent', jsonb_build_object( + 'lifecycle_state', agent_raw.lifecycle_state, + 'computed', agent_status.status + ), + 'app', jsonb_build_object( + 'health', app_raw.health, + 'computed', app_status.status + ) + ) AS status_debug, + task_app.*, + agent_raw.lifecycle_state AS workspace_agent_lifecycle_state, + app_raw.health AS workspace_app_health, + task_owner.* + FROM + tasks + CROSS JOIN LATERAL ( + SELECT + vu.username AS owner_username, + vu.name AS owner_name, + vu.avatar_url AS owner_avatar_url + FROM + visible_users vu + WHERE + vu.id = tasks.owner_id + ) task_owner + LEFT JOIN LATERAL ( + SELECT + task_app.workspace_build_number, + task_app.workspace_agent_id, + task_app.workspace_app_id + FROM + task_workspace_apps task_app + WHERE + task_id = tasks.id + ORDER BY + task_app.workspace_build_number DESC + LIMIT 1 + ) task_app ON TRUE + + -- Join the raw data for computing task status. + LEFT JOIN LATERAL ( + SELECT + workspace_build.transition, + provisioner_job.job_status, + workspace_build.job_id + FROM + workspace_builds workspace_build + JOIN + provisioner_jobs provisioner_job + ON provisioner_job.id = workspace_build.job_id + WHERE + workspace_build.workspace_id = tasks.workspace_id + AND workspace_build.build_number = task_app.workspace_build_number + ) latest_build_raw ON TRUE + LEFT JOIN LATERAL ( + SELECT + workspace_agent.lifecycle_state + FROM + workspace_agents workspace_agent + WHERE + workspace_agent.id = task_app.workspace_agent_id + ) agent_raw ON TRUE + LEFT JOIN LATERAL ( + SELECT + workspace_app.health + FROM + workspace_apps workspace_app + WHERE + workspace_app.id = task_app.workspace_app_id + ) app_raw ON TRUE + + -- Compute the status for each component. + CROSS JOIN LATERAL ( + SELECT + CASE + WHEN latest_build_raw.job_status IS NULL THEN 'pending'::task_status + WHEN latest_build_raw.job_status IN ('failed', 'canceling', 'canceled') THEN 'error'::task_status + WHEN + latest_build_raw.transition IN ('stop', 'delete') + AND latest_build_raw.job_status = 'succeeded' THEN 'paused'::task_status + WHEN + latest_build_raw.transition = 'start' + AND latest_build_raw.job_status = 'pending' THEN 'initializing'::task_status + -- Build is running or done, defer to agent/app status. + WHEN + latest_build_raw.transition = 'start' + AND latest_build_raw.job_status IN ('running', 'succeeded') THEN 'active'::task_status + ELSE 'unknown'::task_status + END AS status + ) build_status + CROSS JOIN LATERAL ( + SELECT + CASE + -- No agent or connecting. + WHEN + agent_raw.lifecycle_state IS NULL + OR agent_raw.lifecycle_state IN ('created', 'starting') THEN 'initializing'::task_status + -- Agent is running, defer to app status. + -- NOTE(mafredri): The start_error/start_timeout states means connected, but some startup script failed. + -- This may or may not affect the task status but this has to be caught by app health check. + WHEN agent_raw.lifecycle_state IN ('ready', 'start_timeout', 'start_error') THEN 'active'::task_status + -- If the agent is shutting down or turned off, this is an unknown state because we would expect a stop + -- build to be running. + -- This is essentially equal to: `IN ('shutting_down', 'shutdown_timeout', 'shutdown_error', 'off')`, + -- but we cannot use them because the values were added in a migration. + WHEN agent_raw.lifecycle_state NOT IN ('created', 'starting', 'ready', 'start_timeout', 'start_error') THEN 'unknown'::task_status + ELSE 'unknown'::task_status + END AS status + ) agent_status + CROSS JOIN LATERAL ( + SELECT + CASE + WHEN app_raw.health = 'initializing' THEN 'initializing'::task_status + WHEN app_raw.health = 'unhealthy' THEN 'error'::task_status + WHEN app_raw.health IN ('healthy', 'disabled') THEN 'active'::task_status + ELSE 'unknown'::task_status + END AS status + ) app_status + WHERE + tasks.deleted_at IS NULL; diff --git a/coderd/database/models.go b/coderd/database/models.go index 5cce4d95aa7f5..af901c31ccad5 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -4208,23 +4208,26 @@ type TailnetTunnel struct { } type Task struct { - ID uuid.UUID `db:"id" json:"id"` - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` - Name string `db:"name" json:"name"` - WorkspaceID uuid.NullUUID `db:"workspace_id" json:"workspace_id"` - TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` - TemplateParameters json.RawMessage `db:"template_parameters" json:"template_parameters"` - Prompt string `db:"prompt" json:"prompt"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - DeletedAt sql.NullTime `db:"deleted_at" json:"deleted_at"` - Status TaskStatus `db:"status" json:"status"` - WorkspaceBuildNumber sql.NullInt32 `db:"workspace_build_number" json:"workspace_build_number"` - WorkspaceAgentID uuid.NullUUID `db:"workspace_agent_id" json:"workspace_agent_id"` - WorkspaceAppID uuid.NullUUID `db:"workspace_app_id" json:"workspace_app_id"` - OwnerUsername string `db:"owner_username" json:"owner_username"` - OwnerName string `db:"owner_name" json:"owner_name"` - OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"` + ID uuid.UUID `db:"id" json:"id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + Name string `db:"name" json:"name"` + WorkspaceID uuid.NullUUID `db:"workspace_id" json:"workspace_id"` + TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` + TemplateParameters json.RawMessage `db:"template_parameters" json:"template_parameters"` + Prompt string `db:"prompt" json:"prompt"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + DeletedAt sql.NullTime `db:"deleted_at" json:"deleted_at"` + Status TaskStatus `db:"status" json:"status"` + StatusDebug json.RawMessage `db:"status_debug" json:"status_debug"` + WorkspaceBuildNumber sql.NullInt32 `db:"workspace_build_number" json:"workspace_build_number"` + WorkspaceAgentID uuid.NullUUID `db:"workspace_agent_id" json:"workspace_agent_id"` + WorkspaceAppID uuid.NullUUID `db:"workspace_app_id" json:"workspace_app_id"` + WorkspaceAgentLifecycleState NullWorkspaceAgentLifecycleState `db:"workspace_agent_lifecycle_state" json:"workspace_agent_lifecycle_state"` + WorkspaceAppHealth NullWorkspaceAppHealth `db:"workspace_app_health" json:"workspace_app_health"` + OwnerUsername string `db:"owner_username" json:"owner_username"` + OwnerName string `db:"owner_name" json:"owner_name"` + OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"` } type TaskTable struct { diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 773f944756576..16ff6c3487163 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -6664,6 +6664,23 @@ func TestTasksWithStatusView(t *testing.T) { StartedAt: sql.NullTime{Valid: true, Time: dbtime.Now()}, CompletedAt: sql.NullTime{Valid: true, Time: dbtime.Now()}, } + case database.ProvisionerJobStatusCanceling: + jobParams = database.ProvisionerJob{ + OrganizationID: org.ID, + Type: database.ProvisionerJobTypeWorkspaceBuild, + InitiatorID: user.ID, + StartedAt: sql.NullTime{Valid: true, Time: dbtime.Now()}, + CanceledAt: sql.NullTime{Valid: true, Time: dbtime.Now()}, + } + case database.ProvisionerJobStatusCanceled: + jobParams = database.ProvisionerJob{ + OrganizationID: org.ID, + Type: database.ProvisionerJobTypeWorkspaceBuild, + InitiatorID: user.ID, + StartedAt: sql.NullTime{Valid: true, Time: dbtime.Now()}, + CompletedAt: sql.NullTime{Valid: true, Time: dbtime.Now()}, + CanceledAt: sql.NullTime{Valid: true, Time: dbtime.Now()}, + } default: t.Errorf("invalid build status: %v", buildStatus) } @@ -6816,6 +6833,28 @@ func TestTasksWithStatusView(t *testing.T) { expectWorkspaceAgentValid: false, expectWorkspaceAppValid: false, }, + { + name: "CancelingBuild", + buildStatus: database.ProvisionerJobStatusCanceling, + buildTransition: database.WorkspaceTransitionStart, + expectedStatus: database.TaskStatusError, + description: "Latest workspace build is canceling", + expectBuildNumberValid: true, + expectBuildNumber: 1, + expectWorkspaceAgentValid: false, + expectWorkspaceAppValid: false, + }, + { + name: "CanceledBuild", + buildStatus: database.ProvisionerJobStatusCanceled, + buildTransition: database.WorkspaceTransitionStart, + expectedStatus: database.TaskStatusError, + description: "Latest workspace build was canceled", + expectBuildNumberValid: true, + expectBuildNumber: 1, + expectWorkspaceAgentValid: false, + expectWorkspaceAppValid: false, + }, { name: "StoppedWorkspace", buildStatus: database.ProvisionerJobStatusSucceeded, @@ -6943,24 +6982,26 @@ func TestTasksWithStatusView(t *testing.T) { buildStatus: database.ProvisionerJobStatusSucceeded, buildTransition: database.WorkspaceTransitionStart, agentState: database.WorkspaceAgentLifecycleStateStartTimeout, - expectedStatus: database.TaskStatusUnknown, - description: "Agent start timed out", + appHealths: []database.WorkspaceAppHealth{database.WorkspaceAppHealthHealthy}, + expectedStatus: database.TaskStatusActive, + description: "Agent start timed out but app is healthy, defer to app", expectBuildNumberValid: true, expectBuildNumber: 1, expectWorkspaceAgentValid: true, - expectWorkspaceAppValid: false, + expectWorkspaceAppValid: true, }, { name: "AgentStartError", buildStatus: database.ProvisionerJobStatusSucceeded, buildTransition: database.WorkspaceTransitionStart, agentState: database.WorkspaceAgentLifecycleStateStartError, - expectedStatus: database.TaskStatusUnknown, - description: "Agent failed to start", + appHealths: []database.WorkspaceAppHealth{database.WorkspaceAppHealthHealthy}, + expectedStatus: database.TaskStatusActive, + description: "Agent start failed but app is healthy, defer to app", expectBuildNumberValid: true, expectBuildNumber: 1, expectWorkspaceAgentValid: true, - expectWorkspaceAppValid: false, + expectWorkspaceAppValid: true, }, { name: "AgentShuttingDown", @@ -7081,6 +7122,8 @@ func TestTasksWithStatusView(t *testing.T) { got, err := db.GetTaskByID(ctx, task.ID) require.NoError(t, err) + t.Logf("Task status debug: %s", got.StatusDebug) + require.Equal(t, tt.expectedStatus, got.Status) require.Equal(t, tt.expectBuildNumberValid, got.WorkspaceBuildNumber.Valid) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 4e8f3d36b2bc7..21cb7b1874b5e 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -13072,7 +13072,7 @@ func (q *sqlQuerier) DeleteTask(ctx context.Context, arg DeleteTaskParams) (Task } const getTaskByID = `-- name: GetTaskByID :one -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE id = $1::uuid +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE id = $1::uuid ` func (q *sqlQuerier) GetTaskByID(ctx context.Context, id uuid.UUID) (Task, error) { @@ -13090,9 +13090,12 @@ func (q *sqlQuerier) GetTaskByID(ctx context.Context, id uuid.UUID) (Task, error &i.CreatedAt, &i.DeletedAt, &i.Status, + &i.StatusDebug, &i.WorkspaceBuildNumber, &i.WorkspaceAgentID, &i.WorkspaceAppID, + &i.WorkspaceAgentLifecycleState, + &i.WorkspaceAppHealth, &i.OwnerUsername, &i.OwnerName, &i.OwnerAvatarUrl, @@ -13101,7 +13104,7 @@ func (q *sqlQuerier) GetTaskByID(ctx context.Context, id uuid.UUID) (Task, error } const getTaskByOwnerIDAndName = `-- name: GetTaskByOwnerIDAndName :one -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id, owner_username, owner_name, owner_avatar_url FROM tasks_with_status +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE owner_id = $1::uuid AND deleted_at IS NULL @@ -13128,9 +13131,12 @@ func (q *sqlQuerier) GetTaskByOwnerIDAndName(ctx context.Context, arg GetTaskByO &i.CreatedAt, &i.DeletedAt, &i.Status, + &i.StatusDebug, &i.WorkspaceBuildNumber, &i.WorkspaceAgentID, &i.WorkspaceAppID, + &i.WorkspaceAgentLifecycleState, + &i.WorkspaceAppHealth, &i.OwnerUsername, &i.OwnerName, &i.OwnerAvatarUrl, @@ -13139,7 +13145,7 @@ func (q *sqlQuerier) GetTaskByOwnerIDAndName(ctx context.Context, arg GetTaskByO } const getTaskByWorkspaceID = `-- name: GetTaskByWorkspaceID :one -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE workspace_id = $1::uuid +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status WHERE workspace_id = $1::uuid ` func (q *sqlQuerier) GetTaskByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (Task, error) { @@ -13157,9 +13163,12 @@ func (q *sqlQuerier) GetTaskByWorkspaceID(ctx context.Context, workspaceID uuid. &i.CreatedAt, &i.DeletedAt, &i.Status, + &i.StatusDebug, &i.WorkspaceBuildNumber, &i.WorkspaceAgentID, &i.WorkspaceAppID, + &i.WorkspaceAgentLifecycleState, + &i.WorkspaceAppHealth, &i.OwnerUsername, &i.OwnerName, &i.OwnerAvatarUrl, @@ -13216,7 +13225,7 @@ func (q *sqlQuerier) InsertTask(ctx context.Context, arg InsertTaskParams) (Task } const listTasks = `-- name: ListTasks :many -SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, workspace_build_number, workspace_agent_id, workspace_app_id, owner_username, owner_name, owner_avatar_url FROM tasks_with_status tws +SELECT id, organization_id, owner_id, name, workspace_id, template_version_id, template_parameters, prompt, created_at, deleted_at, status, status_debug, workspace_build_number, workspace_agent_id, workspace_app_id, workspace_agent_lifecycle_state, workspace_app_health, owner_username, owner_name, owner_avatar_url FROM tasks_with_status tws WHERE tws.deleted_at IS NULL AND CASE WHEN $1::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.owner_id = $1::UUID ELSE TRUE END AND CASE WHEN $2::UUID != '00000000-0000-0000-0000-000000000000' THEN tws.organization_id = $2::UUID ELSE TRUE END @@ -13251,9 +13260,12 @@ func (q *sqlQuerier) ListTasks(ctx context.Context, arg ListTasksParams) ([]Task &i.CreatedAt, &i.DeletedAt, &i.Status, + &i.StatusDebug, &i.WorkspaceBuildNumber, &i.WorkspaceAgentID, &i.WorkspaceAppID, + &i.WorkspaceAgentLifecycleState, + &i.WorkspaceAppHealth, &i.OwnerUsername, &i.OwnerName, &i.OwnerAvatarUrl, diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index af700c14519be..2386a4091f2d6 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -106,6 +106,15 @@ sql: # Workaround for sqlc not interpreting the left join correctly. - column: "tasks_with_status.workspace_build_number" go_type: "database/sql.NullInt32" + - column: "tasks_with_status.status" + go_type: + type: "TaskStatus" + - column: "tasks_with_status.workspace_agent_lifecycle_state" + go_type: + type: "NullWorkspaceAgentLifecycleState" + - column: "tasks_with_status.workspace_app_health" + go_type: + type: "NullWorkspaceAppHealth" rename: group_member: GroupMemberTable group_members_expanded: GroupMember From fa314fe7e5cea5ec98eb86656f47a9d832eac66a Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 14 Nov 2025 20:05:29 +0200 Subject: [PATCH 151/255] fix(coderd/database): rename duplicate migration 397 to 398 (#20783) Fix duplicate migration from #20683. --- ...atus_view.down.sql => 000398_update_task_status_view.down.sql} | 0 ...k_status_view.up.sql => 000398_update_task_status_view.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000397_update_task_status_view.down.sql => 000398_update_task_status_view.down.sql} (100%) rename coderd/database/migrations/{000397_update_task_status_view.up.sql => 000398_update_task_status_view.up.sql} (100%) diff --git a/coderd/database/migrations/000397_update_task_status_view.down.sql b/coderd/database/migrations/000398_update_task_status_view.down.sql similarity index 100% rename from coderd/database/migrations/000397_update_task_status_view.down.sql rename to coderd/database/migrations/000398_update_task_status_view.down.sql diff --git a/coderd/database/migrations/000397_update_task_status_view.up.sql b/coderd/database/migrations/000398_update_task_status_view.up.sql similarity index 100% rename from coderd/database/migrations/000397_update_task_status_view.up.sql rename to coderd/database/migrations/000398_update_task_status_view.up.sql From 5ee39e88a3340725a04488ff777b091650e0ae3e Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:49:06 +0100 Subject: [PATCH 152/255] chore: rename cmux to mux in the dogfood template (#20787) --- dogfood/coder/main.tf | 14 +++++++------- site/src/theme/icons.json | 2 +- site/static/icon/{cmux.svg => mux.svg} | 0 3 files changed, 8 insertions(+), 8 deletions(-) rename site/static/icon/{cmux.svg => mux.svg} (100%) diff --git a/dogfood/coder/main.tf b/dogfood/coder/main.tf index d462711bfa924..88a869dbad0ec 100644 --- a/dogfood/coder/main.tf +++ b/dogfood/coder/main.tf @@ -268,16 +268,16 @@ data "coder_parameter" "ide_choices" { form_type = "multi-select" mutable = true description = "Choose one or more IDEs to enable in your workspace" - default = jsonencode(["vscode", "code-server", "cursor", "cmux"]) + default = jsonencode(["vscode", "code-server", "cursor", "mux"]) option { name = "VS Code Desktop" value = "vscode" icon = "/icon/code.svg" } option { - name = "cmux" - value = "cmux" - icon = "/icon/cmux.svg" + name = "mux" + value = "mux" + icon = "/icon/mux.svg" } option { name = "code-server" @@ -375,9 +375,9 @@ module "personalize" { agent_id = coder_agent.dev.id } -module "cmux" { - count = contains(jsondecode(data.coder_parameter.ide_choices.value), "cmux") ? data.coder_workspace.me.start_count : 0 - source = "registry.coder.com/coder/cmux/coder" +module "mux" { + count = contains(jsondecode(data.coder_parameter.ide_choices.value), "mux") ? data.coder_workspace.me.start_count : 0 + source = "registry.coder.com/coder/mux/coder" version = "1.0.0" agent_id = coder_agent.dev.id subdomain = true diff --git a/site/src/theme/icons.json b/site/src/theme/icons.json index 408ba3e3c749b..fb055741c7585 100644 --- a/site/src/theme/icons.json +++ b/site/src/theme/icons.json @@ -21,7 +21,6 @@ "centos.svg", "claude.svg", "clion.svg", - "cmux.svg", "code-insiders.svg", "code.svg", "coder.svg", @@ -83,6 +82,7 @@ "microsoft-teams.svg", "microsoft.svg", "mlflow.svg", + "mux.svg", "nextflow.svg", "nexus-repository.svg", "nix.svg", diff --git a/site/static/icon/cmux.svg b/site/static/icon/mux.svg similarity index 100% rename from site/static/icon/cmux.svg rename to site/static/icon/mux.svg From 897286f33520f45e92f37c452c987124231319f2 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:00:07 +1100 Subject: [PATCH 153/255] test: fix flake in scaletest/workspaceupdates/TestRun (#20773) Closes https://github.com/coder/internal/issues/1127 In the workspace updates scaletest load generator, we end the test once all clients have seen a workspace update for their workspace. These workspace updates are generated when the workspace is created, NOT when the workspace build has finished. This means when the runner goes to clean up the workspaces, they may still be building. The runner attempts to address this by cancelling the build, but that fails with a 403 since the runner users don't have permission. The fix is to simply always wait for the build to finish, regardless of whether we were able to successfully cancel it. In this test it's probably faster to just wait for the build to finish then the overhead of cancelling it, so that's what I've gone with here. --- scaletest/workspacebuild/run.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scaletest/workspacebuild/run.go b/scaletest/workspacebuild/run.go index f05173c6886bc..fd3f1be54b9b6 100644 --- a/scaletest/workspacebuild/run.go +++ b/scaletest/workspacebuild/run.go @@ -147,12 +147,12 @@ func (r *CleanupRunner) Run(ctx context.Context, _ string, logs io.Writer) error if err == nil && build.Job.Status.Active() { // mark the build as canceled logger.Info(ctx, "canceling workspace build", slog.F("build_id", build.ID), slog.F("workspace_id", r.workspaceID)) - if err = r.client.CancelWorkspaceBuild(ctx, build.ID, codersdk.CancelWorkspaceBuildParams{}); err == nil { - // Wait for the job to cancel before we delete it - _ = waitForBuild(ctx, logs, r.client, build.ID) // it will return a "build canceled" error - } else { - logger.Warn(ctx, "failed to cancel workspace build, attempting to delete anyway", slog.Error(err)) + if err = r.client.CancelWorkspaceBuild(ctx, build.ID, codersdk.CancelWorkspaceBuildParams{}); err != nil { + logger.Warn(ctx, "failed to cancel workspace build", slog.Error(err)) } + // Wait for either the build or the cancellation to finish + // either is necessary or we'll fail at the delete step. + _ = waitForBuild(ctx, logs, r.client, build.ID) // it will return a "build canceled" error } else { logger.Warn(ctx, "unable to lookup latest workspace build, attempting to delete anyway", slog.Error(err)) } From 04cf5f8690cae9dfb77fe0365970fc2573d82720 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:14:37 +0000 Subject: [PATCH 154/255] chore: bump google.golang.org/api from 0.255.0 to 0.256.0 (#20794) Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.255.0 to 0.256.0.
Changelog

Sourced from google.golang.org/api's changelog.

0.256.0 (2025-11-10)

Features

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/api&package-manager=go_modules&previous-version=0.255.0&new-version=0.256.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 49bdd7805982c..1b7f5786df92c 100644 --- a/go.mod +++ b/go.mod @@ -206,7 +206,7 @@ require ( golang.org/x/text v0.30.0 golang.org/x/tools v0.38.0 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da - google.golang.org/api v0.255.0 + google.golang.org/api v0.256.0 google.golang.org/grpc v1.76.0 google.golang.org/protobuf v1.36.10 gopkg.in/DataDog/dd-trace-go.v1 v1.74.0 @@ -324,7 +324,7 @@ require ( github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect @@ -453,7 +453,7 @@ require ( google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect gopkg.in/ini.v1 v1.67.0 // indirect howett.net/plist v1.0.0 // indirect kernel.org/pub/linux/libs/security/libcap/psx v1.2.73 // indirect diff --git a/go.sum b/go.sum index 455f78d661fa4..0bf3303563fae 100644 --- a/go.sum +++ b/go.sum @@ -1344,8 +1344,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= -github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -2543,8 +2543,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.255.0 h1:OaF+IbRwOottVCYV2wZan7KUq7UeNUQn1BcPc4K7lE4= -google.golang.org/api v0.255.0/go.mod h1:d1/EtvCLdtiWEV4rAEHDHGh2bCnqsWhw+M8y2ECN4a8= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2689,8 +2689,8 @@ google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJ google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= From 1c15534c983d036a81a4c303adabe403a70fc05c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:14:53 +0000 Subject: [PATCH 155/255] chore: bump the x group with 6 updates (#20792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps the x group with 6 updates: | Package | From | To | | --- | --- | --- | | [golang.org/x/crypto](https://github.com/golang/crypto) | `0.43.0` | `0.44.0` | | [golang.org/x/mod](https://github.com/golang/mod) | `0.29.0` | `0.30.0` | | [golang.org/x/net](https://github.com/golang/net) | `0.46.0` | `0.47.0` | | [golang.org/x/term](https://github.com/golang/term) | `0.36.0` | `0.37.0` | | [golang.org/x/text](https://github.com/golang/text) | `0.30.0` | `0.31.0` | | [golang.org/x/tools](https://github.com/golang/tools) | `0.38.0` | `0.39.0` | Updates `golang.org/x/crypto` from 0.43.0 to 0.44.0
Commits
  • 122a78f go.mod: update golang.org/x dependencies
  • c0531f9 all: eliminate vet diagnostics
  • 0997000 all: fix some comments
  • 017a1aa chacha20poly1305: panic on dst and additionalData overlap
  • cf29fa9 sha3: make it mostly a wrapper around crypto/sha3
  • 0b7aa0c ssh: use reflect.TypeFor instead of reflect.TypeOf
  • 1faea29 all: fix some typos in comment
  • See full diff in compare view

Updates `golang.org/x/mod` from 0.29.0 to 0.30.0
Commits
  • 7416265 go.mod: update golang.org/x dependencies
  • 5517a71 all: fix some comments
  • b6cdd1a modfile: use reflect.TypeFor instead of reflect.TypeOf
  • See full diff in compare view

Updates `golang.org/x/net` from 0.46.0 to 0.47.0
Commits
  • 9a29643 go.mod: update golang.org/x dependencies
  • 07cefd8 context: deprecate
  • 5ac9dac publicsuffix: don't treat ip addresses as domain names
  • d1f64cc quic: use testing/synctest
  • fff0469 http2: document that RFC 7540 prioritization does not work with small payloads
  • f35e3a4 http2: fix weight overflow in RFC 7540 write scheduler
  • 89adc90 http2: fix typo referring to RFC 9218 as RFC 9128 instead
  • 8d76a2c quic: don't defer MAX_STREAMS frames indefinitely
  • 027f8b7 quic: fix expected ACK Delay in client's ACK after HANDSHAKE_DONE
  • dec9fe7 dns/dnsmessage: update SVCB packing to prohibit name compression
  • Additional commits viewable in compare view

Updates `golang.org/x/term` from 0.36.0 to 0.37.0
Commits

Updates `golang.org/x/text` from 0.30.0 to 0.31.0
Commits
  • e7ff6b3 go.mod: update golang.org/x dependencies
  • fbf012b all: use reflect.TypeFor instead of reflect.TypeOf
  • See full diff in compare view

Updates `golang.org/x/tools` from 0.38.0 to 0.39.0
Commits
  • 034e59c internal/analysis/analyzerutil: fix FileUsesGoVersion
  • 076bd80 gopls/internal/filewatcher: retry directory reading upon failure
  • 605803f go/analysis/passes/loopclosure: simplify using IsMethodNamed
  • 2c6e03f internal/testenv: allow Apple diff too
  • 82112c0 gopls/internal/settings: correct git issue for fieldalignment warning
  • 1f97856 go.mod: update golang.org/x dependencies
  • c24121c go/analysis/passes/modernize: stditerators: even better name heuristic
  • edb9587 go/analysis/passes/modernize: stditerators: better name heuristic
  • 2f6a4f9 go/analysis/passes/modernize: forvar: handle "if v := v; cond {"
  • efd8c43 go/analysis: don't apply fixes to generated files
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 1b7f5786df92c..97112267cb267 100644 --- a/go.mod +++ b/go.mod @@ -195,16 +195,16 @@ require ( go.uber.org/goleak v1.3.1-0.20240429205332-517bace7cc29 go.uber.org/mock v0.6.0 go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 - golang.org/x/crypto v0.43.0 + golang.org/x/crypto v0.44.0 golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 - golang.org/x/mod v0.29.0 - golang.org/x/net v0.46.0 + golang.org/x/mod v0.30.0 + golang.org/x/net v0.47.0 golang.org/x/oauth2 v0.33.0 golang.org/x/sync v0.18.0 golang.org/x/sys v0.38.0 - golang.org/x/term v0.36.0 - golang.org/x/text v0.30.0 - golang.org/x/tools v0.38.0 + golang.org/x/term v0.37.0 + golang.org/x/text v0.31.0 + golang.org/x/tools v0.39.0 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da google.golang.org/api v0.256.0 google.golang.org/grpc v1.76.0 diff --git a/go.sum b/go.sum index 0bf3303563fae..9ff4c3651567a 100644 --- a/go.sum +++ b/go.sum @@ -2057,8 +2057,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2123,8 +2123,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2187,8 +2187,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2359,8 +2359,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2383,8 +2383,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2458,8 +2458,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 430c8c2dd292e06c745aff7d57cf50b1ba228d9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:15:11 +0000 Subject: [PATCH 156/255] chore: bump github.com/anthropics/anthropic-sdk-go from 1.17.0 to 1.18.0 (#20795) Bumps [github.com/anthropics/anthropic-sdk-go](https://github.com/anthropics/anthropic-sdk-go) from 1.17.0 to 1.18.0.
Release notes

Sourced from github.com/anthropics/anthropic-sdk-go's releases.

v1.18.0

1.18.0 (2025-11-14)

Full Changelog: v1.17.0...v1.18.0

Features

  • api: add support for structured outputs beta (fb9cfb4)

Chores

Changelog

Sourced from github.com/anthropics/anthropic-sdk-go's changelog.

1.18.0 (2025-11-14)

Full Changelog: v1.17.0...v1.18.0

Features

  • api: add support for structured outputs beta (fb9cfb4)

Chores

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/anthropics/anthropic-sdk-go&package-manager=go_modules&previous-version=1.17.0&new-version=1.18.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 97112267cb267..924959e860e69 100644 --- a/go.mod +++ b/go.mod @@ -473,7 +473,7 @@ require ( ) require ( - github.com/anthropics/anthropic-sdk-go v1.17.0 + github.com/anthropics/anthropic-sdk-go v1.18.0 github.com/brianvoe/gofakeit/v7 v7.9.0 github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 github.com/coder/aibridge v0.1.7 diff --git a/go.sum b/go.sum index 9ff4c3651567a..c0ec89d57cdae 100644 --- a/go.sum +++ b/go.sum @@ -726,8 +726,8 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/anthropics/anthropic-sdk-go v1.17.0 h1:BwK8ApcmaAUkvZTiQE0yi3R9XneEFskDIjLTmOAFZxQ= -github.com/anthropics/anthropic-sdk-go v1.17.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= +github.com/anthropics/anthropic-sdk-go v1.18.0 h1:jfxRA7AqZoCm83nHO/OVQp8xuwjUKtBziEdMbfmofHU= +github.com/anthropics/anthropic-sdk-go v1.18.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= From 0b0813e30c5305574b12094bc24ce00f4033f2b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:19:23 +0000 Subject: [PATCH 157/255] ci: bump the github-actions group with 3 updates (#20796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 3 updates: [crate-ci/typos](https://github.com/crate-ci/typos), [peter-evans/repository-dispatch](https://github.com/peter-evans/repository-dispatch) and [github/codeql-action](https://github.com/github/codeql-action). Updates `crate-ci/typos` from 1.39.0 to 1.39.2
Release notes

Sourced from crate-ci/typos's releases.

v1.39.2

[1.39.2] - 2025-11-13

Fixes

  • Don't offer entry as a correction for entrys

v1.39.1

[1.39.1] - 2025-11-12

Features

  • Make --help more vibrant
Changelog

Sourced from crate-ci/typos's changelog.

Change Log

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

[Unreleased] - ReleaseDate

[1.39.2] - 2025-11-13

Fixes

  • Don't offer entry as a correction for entrys

[1.39.1] - 2025-11-12

Features

  • Make --help more vibrant

[1.39.0] - 2025-10-31

Features

Fixes

  • When a typo is pluralized, prefer pluralized corrections

[1.38.1] - 2025-10-07

Fixes

  • Ignore common golang identifiers

[1.38.0] - 2025-10-06

Features

  • Update type list

Fixes

  • Don't correct typ
  • Consistently error on unused config fields

[1.37.3] - 2025-10-06

... (truncated)

Commits
  • 626c4be chore: Release
  • c6b458d docs: Update changelog
  • eed0419 Merge pull request #1423 from epage/entrys
  • 40383f4 fix(dict): Don't offer 'entry' as a correction for 'entrys'
  • 1af53e3 chore: Release
  • e5d291b docs: Update changelog
  • 55474f5 Merge pull request #1417 from starsep/colorful_help
  • 78b9375 feat: Enable colors for typos --help
  • 308f8f5 Merge pull request #1409 from crate-ci/renovate/actions-download-artifact-6.x
  • cf03418 Merge pull request #1410 from crate-ci/renovate/actions-setup-python-6.x
  • Additional commits viewable in compare view

Updates `peter-evans/repository-dispatch` from 4.0.0 to 4.0.1
Release notes

Sourced from peter-evans/repository-dispatch's releases.

v4.0.1

What's Changed

Full Changelog: https://github.com/peter-evans/repository-dispatch/compare/v4.0.0...v4.0.1

Commits
  • 28959ce Fix node version in actions.yml (#433)
  • 25d29c2 build(deps-dev): bump @​types/node in the npm group (#432)
  • 830136c build(deps): bump the github-actions group with 3 updates (#431)
  • 2c856c6 ci: update dependabot config
  • 6673907 build(deps-dev): bump @​types/node from 18.19.127 to 18.19.129 (#429)
  • 952a211 build(deps): bump peter-evans/repository-dispatch from 3 to 4 (#428)
  • See full diff in compare view

Updates `github/codeql-action` from 4.31.2 to 4.31.3
Release notes

Sourced from github/codeql-action's releases.

v4.31.3

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

4.31.3 - 13 Nov 2025

  • CodeQL Action v3 will be deprecated in December 2026. The Action now logs a warning for customers who are running v3 but could be running v4. For more information, see Upcoming deprecation of CodeQL Action v3.
  • Update default CodeQL bundle version to 2.23.5. #3288

See the full CHANGELOG.md for more information.

Changelog

Sourced from github/codeql-action's changelog.

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

[UNRELEASED]

No user facing changes.

4.31.3 - 13 Nov 2025

  • CodeQL Action v3 will be deprecated in December 2026. The Action now logs a warning for customers who are running v3 but could be running v4. For more information, see Upcoming deprecation of CodeQL Action v3.
  • Update default CodeQL bundle version to 2.23.5. #3288

4.31.2 - 30 Oct 2025

No user facing changes.

4.31.1 - 30 Oct 2025

  • The add-snippets input has been removed from the analyze action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced.

4.31.0 - 24 Oct 2025

  • Bump minimum CodeQL bundle version to 2.17.6. #3223
  • When SARIF files are uploaded by the analyze or upload-sarif actions, the CodeQL Action automatically performs post-processing steps to prepare the data for the upload. Previously, these post-processing steps were only performed before an upload took place. We are now changing this so that the post-processing steps will always be performed, even when the SARIF files are not uploaded. This does not change anything for the upload-sarif action. For analyze, this may affect Advanced Setup for CodeQL users who specify a value other than always for the upload input. #3222

4.30.9 - 17 Oct 2025

  • Update default CodeQL bundle version to 2.23.3. #3205
  • Experimental: A new setup-codeql action has been added which is similar to init, except it only installs the CodeQL CLI and does not initialize a database. Do not use this in production as it is part of an internal experiment and subject to change at any time. #3204

4.30.8 - 10 Oct 2025

No user facing changes.

4.30.7 - 06 Oct 2025

  • [v4+ only] The CodeQL Action now runs on Node.js v24. #3169

3.30.6 - 02 Oct 2025

  • Update default CodeQL bundle version to 2.23.2. #3168

3.30.5 - 26 Sep 2025

  • We fixed a bug that was introduced in 3.30.4 with upload-sarif which resulted in files without a .sarif extension not getting uploaded. #3160

3.30.4 - 25 Sep 2025

  • We have improved the CodeQL Action's ability to validate that the workflow it is used in does not use different versions of the CodeQL Action for different workflow steps. Mixing different versions of the CodeQL Action in the same workflow is unsupported and can lead to unpredictable results. A warning will now be emitted from the codeql-action/init step if different versions of the CodeQL Action are detected in the workflow file. Additionally, an error will now be thrown by the other CodeQL Action steps if they load a configuration file that was generated by a different version of the codeql-action/init step. #3099 and #3100

... (truncated)

Commits
  • 014f16e Merge pull request #3293 from github/update-v4.31.3-8c10e89c7
  • 14d898e Update changelog for v4.31.3
  • 8c10e89 Merge pull request #3288 from github/update-bundle/codeql-bundle-v2.23.5
  • 9777b01 Merge branch 'main' into update-bundle/codeql-bundle-v2.23.5
  • 456a74a Merge pull request #3289 from github/mbg/ci/setup-dotnet
  • 3fac49c Update remaining workflows
  • 38a3a72 Enable installDotNet in all workflows that analyse C#
  • 58c9eb6 Add global.json
  • f20e021 Add support for adding setup-dotnet steps to sync.sh
  • 8d3d400 Add changelog note
  • Additional commits viewable in compare view

Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | crate-ci/typos | [>= 1.30.a, < 1.31] |
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/security.yaml | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 54e099fe9efba..c4545438790e8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -191,7 +191,7 @@ jobs: # Check for any typos - name: Check for typos - uses: crate-ci/typos@07d900b8fa1097806b8adb6391b0d3e0ac2fdea7 # v1.39.0 + uses: crate-ci/typos@626c4bedb751ce0b7f03262ca97ddda9a076ae1c # v1.39.2 with: config: .github/workflows/typos.toml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7302b2f5db86d..be1b56f07256c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -785,7 +785,7 @@ jobs: - name: Send repository-dispatch event if: ${{ !inputs.dry_run }} - uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0 + uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1 with: token: ${{ secrets.CDRCI_GITHUB_TOKEN }} repository: coder/packages diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3c481aa8d191f..392311ad7f7ef 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -47,6 +47,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 + uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 with: sarif_file: results.sarif diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml index 0d5bfeae3e5cf..e40d828c51d5a 100644 --- a/.github/workflows/security.yaml +++ b/.github/workflows/security.yaml @@ -40,7 +40,7 @@ jobs: uses: ./.github/actions/setup-go - name: Initialize CodeQL - uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 + uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 with: languages: go, javascript @@ -50,7 +50,7 @@ jobs: rm Makefile - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 + uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 - name: Send Slack notification on failure if: ${{ failure() }} @@ -154,7 +154,7 @@ jobs: severity: "CRITICAL,HIGH" - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 + uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 with: sarif_file: trivy-results.sarif category: "Trivy" From ad3e8885e4f6bd4da9ca1a2a7d24cad9755e9523 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:23:46 +0000 Subject: [PATCH 158/255] chore: bump rust from `d9ba801` to `cef0ec9` in /dogfood/coder (#20798) Bumps rust from `d9ba801` to `cef0ec9`. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rust&package-manager=docker&previous-version=slim&new-version=slim)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dogfood/coder/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dogfood/coder/Dockerfile b/dogfood/coder/Dockerfile index 4c0691871d964..5243a46904398 100644 --- a/dogfood/coder/Dockerfile +++ b/dogfood/coder/Dockerfile @@ -1,5 +1,5 @@ # 1.86.0 -FROM rust:slim@sha256:d9ba8014603166915f7e0fcaa9af09df2a1fc30547e75a72c1d34165139f036a AS rust-utils +FROM rust:slim@sha256:cef0ec962e08d8b5dcba05604189e5751c1bd3ec7d12db0a93e4215468d4ac4a AS rust-utils # Install rust helper programs ENV CARGO_INSTALL_ROOT=/tmp/ # Use more reliable mirrors for Debian packages From 355150072b7acb94e3c20421222c712fbec5db58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:23:50 +0000 Subject: [PATCH 159/255] chore: bump ubuntu from `0950623` to `104ae83` in /dogfood/coder (#20797) Bumps ubuntu from `0950623` to `104ae83`. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ubuntu&package-manager=docker&previous-version=jammy&new-version=jammy)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dogfood/coder/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dogfood/coder/Dockerfile b/dogfood/coder/Dockerfile index 5243a46904398..eac4512611212 100644 --- a/dogfood/coder/Dockerfile +++ b/dogfood/coder/Dockerfile @@ -8,7 +8,7 @@ RUN sed -i 's|http://deb.debian.org/debian|http://mirrors.edge.kernel.org/debian RUN apt-get update && apt-get install -y libssl-dev openssl pkg-config build-essential RUN cargo install jj-cli typos-cli watchexec-cli -FROM ubuntu:jammy@sha256:09506232a8004baa32c47d68f1e5c307d648fdd59f5e7eaa42aaf87914100db3 AS go +FROM ubuntu:jammy@sha256:104ae83764a5119017b8e8d6218fa0832b09df65aae7d5a6de29a85d813da2fb AS go # Install Go manually, so that we can control the version ARG GO_VERSION=1.24.10 @@ -102,7 +102,7 @@ RUN curl -L -o protoc.zip https://github.com/protocolbuffers/protobuf/releases/d unzip protoc.zip && \ rm protoc.zip -FROM ubuntu:jammy@sha256:09506232a8004baa32c47d68f1e5c307d648fdd59f5e7eaa42aaf87914100db3 +FROM ubuntu:jammy@sha256:104ae83764a5119017b8e8d6218fa0832b09df65aae7d5a6de29a85d813da2fb SHELL ["/bin/bash", "-c"] From 16b8e6072fd84f45404e3f84bb2b6fea2424b090 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Mon, 17 Nov 2025 13:24:12 +0000 Subject: [PATCH 160/255] fix: set codersdk.Task current_state during task initialization (#20692) ## Problem With the new tasks data model, a task starts with an `initializing` status. However, the API returns `current_state: null` to represent the agent state, causing the frontend to display "No message available". This PR updates `codersdk.Task` to return a `current_state` when the task is initializing with meaningful messages about what's happening during task initialization. **Previous message** Screenshot 2025-11-07 at 09 06 13 **New message** Screenshot 2025-11-12 at 11 00 15 ## Changes - Populate `current_state` with descriptive initialization messages when task status is `initializing` and no valid app status exists for the current build - **dbfake**: Fix `WorkspaceBuild` builder to properly handle pending/running jobs by linking tasks without requiring agent/app resources **Note:** UI Storybook changes to reflect these new messages will be addressed in a follow-up PR. Closes: https://github.com/coder/internal/issues/1063 --- coderd/aitasks.go | 97 +++++++++++--- coderd/aitasks_internal_test.go | 223 +++++++++++++++++++++++++++++++ coderd/aitasks_test.go | 8 +- coderd/database/dbfake/dbfake.go | 14 +- 4 files changed, 319 insertions(+), 23 deletions(-) create mode 100644 coderd/aitasks_internal_test.go diff --git a/coderd/aitasks.go b/coderd/aitasks.go index d5cca9e4f0b3f..ccd8b2dfac5b7 100644 --- a/coderd/aitasks.go +++ b/coderd/aitasks.go @@ -13,6 +13,7 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbtime" @@ -23,6 +24,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/searchquery" "github.com/coder/coder/v2/coderd/taskname" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/codersdk" @@ -270,15 +272,21 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) { func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) codersdk.Task { var taskAgentLifecycle *codersdk.WorkspaceAgentLifecycle var taskAgentHealth *codersdk.WorkspaceAgentHealth + var taskAppHealth *codersdk.WorkspaceAppHealth + + if dbTask.WorkspaceAgentLifecycleState.Valid { + taskAgentLifecycle = ptr.Ref(codersdk.WorkspaceAgentLifecycle(dbTask.WorkspaceAgentLifecycleState.WorkspaceAgentLifecycleState)) + } + if dbTask.WorkspaceAppHealth.Valid { + taskAppHealth = ptr.Ref(codersdk.WorkspaceAppHealth(dbTask.WorkspaceAppHealth.WorkspaceAppHealth)) + } - // If we have an agent ID from the task, find the agent details in the - // workspace. + // If we have an agent ID from the task, find the agent health info if dbTask.WorkspaceAgentID.Valid { findTaskAgentLoop: for _, resource := range ws.LatestBuild.Resources { for _, agent := range resource.Agents { if agent.ID == dbTask.WorkspaceAgentID.UUID { - taskAgentLifecycle = &agent.LifecycleState taskAgentHealth = &agent.Health break findTaskAgentLoop } @@ -286,21 +294,7 @@ func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) cod } } - // Ignore 'latest app status' if it is older than the latest build and the - // latest build is a 'start' transition. This ensures that you don't show a - // stale app status from a previous build. For stop transitions, there is - // still value in showing the latest app status. - var currentState *codersdk.TaskStateEntry - if ws.LatestAppStatus != nil { - if ws.LatestBuild.Transition != codersdk.WorkspaceTransitionStart || ws.LatestAppStatus.CreatedAt.After(ws.LatestBuild.CreatedAt) { - currentState = &codersdk.TaskStateEntry{ - Timestamp: ws.LatestAppStatus.CreatedAt, - State: codersdk.TaskState(ws.LatestAppStatus.State), - Message: ws.LatestAppStatus.Message, - URI: ws.LatestAppStatus.URI, - } - } - } + currentState := deriveTaskCurrentState(dbTask, ws, taskAgentLifecycle, taskAppHealth) return codersdk.Task{ ID: dbTask.ID, @@ -330,6 +324,73 @@ func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) cod } } +// deriveTaskCurrentState determines the current state of a task based on the +// workspace's latest app status and initialization phase. +// Returns nil if no valid state can be determined. +func deriveTaskCurrentState( + dbTask database.Task, + ws codersdk.Workspace, + taskAgentLifecycle *codersdk.WorkspaceAgentLifecycle, + taskAppHealth *codersdk.WorkspaceAppHealth, +) *codersdk.TaskStateEntry { + var currentState *codersdk.TaskStateEntry + + // Ignore 'latest app status' if it is older than the latest build and the + // latest build is a 'start' transition. This ensures that you don't show a + // stale app status from a previous build. For stop transitions, there is + // still value in showing the latest app status. + if ws.LatestAppStatus != nil { + if ws.LatestBuild.Transition != codersdk.WorkspaceTransitionStart || ws.LatestAppStatus.CreatedAt.After(ws.LatestBuild.CreatedAt) { + currentState = &codersdk.TaskStateEntry{ + Timestamp: ws.LatestAppStatus.CreatedAt, + State: codersdk.TaskState(ws.LatestAppStatus.State), + Message: ws.LatestAppStatus.Message, + URI: ws.LatestAppStatus.URI, + } + } + } + + // If no valid agent state was found for the current build and the task is initializing, + // provide a descriptive initialization message. + if currentState == nil && dbTask.Status == database.TaskStatusInitializing { + message := "Initializing workspace" + + switch { + case ws.LatestBuild.Status == codersdk.WorkspaceStatusPending || + ws.LatestBuild.Status == codersdk.WorkspaceStatusStarting: + message = fmt.Sprintf("Workspace is %s", ws.LatestBuild.Status) + case taskAgentLifecycle != nil: + switch { + case *taskAgentLifecycle == codersdk.WorkspaceAgentLifecycleCreated: + message = "Agent is connecting" + case *taskAgentLifecycle == codersdk.WorkspaceAgentLifecycleStarting: + message = "Agent is starting" + case *taskAgentLifecycle == codersdk.WorkspaceAgentLifecycleReady: + if taskAppHealth != nil && *taskAppHealth == codersdk.WorkspaceAppHealthInitializing { + message = "App is initializing" + } else { + // In case the workspace app is not initializing, + // the overall task status should be updated accordingly + message = "Initializing workspace applications" + } + default: + // In case the workspace agent is not initializing, + // the overall task status should be updated accordingly + message = "Initializing workspace agent" + } + } + + currentState = &codersdk.TaskStateEntry{ + Timestamp: ws.LatestBuild.CreatedAt, + State: codersdk.TaskStateWorking, + Message: message, + URI: "", + } + } + + return currentState +} + // @Summary List AI tasks // @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable. // @ID list-tasks diff --git a/coderd/aitasks_internal_test.go b/coderd/aitasks_internal_test.go new file mode 100644 index 0000000000000..0c087c653befd --- /dev/null +++ b/coderd/aitasks_internal_test.go @@ -0,0 +1,223 @@ +package coderd + +import ( + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/util/ptr" + "github.com/coder/coder/v2/codersdk" +) + +func TestDeriveTaskCurrentState_Unit(t *testing.T) { + t.Parallel() + + now := time.Now() + tests := []struct { + name string + task database.Task + agentLifecycle *codersdk.WorkspaceAgentLifecycle + appHealth *codersdk.WorkspaceAppHealth + latestAppStatus *codersdk.WorkspaceAppStatus + latestBuild codersdk.WorkspaceBuild + expectCurrentState bool + expectedTimestamp time.Time + expectedState codersdk.TaskState + expectedMessage string + }{ + { + name: "NoAppStatus", + task: database.Task{ + ID: uuid.New(), + Status: database.TaskStatusActive, + }, + agentLifecycle: nil, + appHealth: nil, + latestAppStatus: nil, + latestBuild: codersdk.WorkspaceBuild{ + Transition: codersdk.WorkspaceTransitionStart, + CreatedAt: now, + }, + expectCurrentState: false, + }, + { + name: "BuildStartTransition_AppStatus_NewerThanBuild", + task: database.Task{ + ID: uuid.New(), + Status: database.TaskStatusActive, + }, + agentLifecycle: nil, + appHealth: nil, + latestAppStatus: &codersdk.WorkspaceAppStatus{ + State: codersdk.WorkspaceAppStatusStateWorking, + Message: "Task is working", + CreatedAt: now.Add(1 * time.Minute), + }, + latestBuild: codersdk.WorkspaceBuild{ + Transition: codersdk.WorkspaceTransitionStart, + CreatedAt: now, + }, + expectCurrentState: true, + expectedTimestamp: now.Add(1 * time.Minute), + expectedState: codersdk.TaskState(codersdk.WorkspaceAppStatusStateWorking), + expectedMessage: "Task is working", + }, + { + name: "BuildStartTransition_StaleAppStatus_OlderThanBuild", + task: database.Task{ + ID: uuid.New(), + Status: database.TaskStatusActive, + }, + agentLifecycle: nil, + appHealth: nil, + latestAppStatus: &codersdk.WorkspaceAppStatus{ + State: codersdk.WorkspaceAppStatusStateComplete, + Message: "Previous task completed", + CreatedAt: now.Add(-1 * time.Minute), + }, + latestBuild: codersdk.WorkspaceBuild{ + Transition: codersdk.WorkspaceTransitionStart, + CreatedAt: now, + }, + expectCurrentState: false, + }, + { + name: "BuildStopTransition", + task: database.Task{ + ID: uuid.New(), + Status: database.TaskStatusActive, + }, + agentLifecycle: nil, + appHealth: nil, + latestAppStatus: &codersdk.WorkspaceAppStatus{ + State: codersdk.WorkspaceAppStatusStateComplete, + Message: "Task completed before stop", + CreatedAt: now.Add(-1 * time.Minute), + }, + latestBuild: codersdk.WorkspaceBuild{ + Transition: codersdk.WorkspaceTransitionStop, + CreatedAt: now, + }, + expectCurrentState: true, + expectedTimestamp: now.Add(-1 * time.Minute), + expectedState: codersdk.TaskState(codersdk.WorkspaceAppStatusStateComplete), + expectedMessage: "Task completed before stop", + }, + { + name: "TaskInitializing_WorkspacePending", + task: database.Task{ + ID: uuid.New(), + Status: database.TaskStatusInitializing, + }, + agentLifecycle: nil, + appHealth: nil, + latestAppStatus: nil, + latestBuild: codersdk.WorkspaceBuild{ + Status: codersdk.WorkspaceStatusPending, + CreatedAt: now, + }, + expectCurrentState: true, + expectedTimestamp: now, + expectedState: codersdk.TaskStateWorking, + expectedMessage: "Workspace is pending", + }, + { + name: "TaskInitializing_WorkspaceStarting", + task: database.Task{ + ID: uuid.New(), + Status: database.TaskStatusInitializing, + }, + agentLifecycle: nil, + appHealth: nil, + latestAppStatus: nil, + latestBuild: codersdk.WorkspaceBuild{ + Status: codersdk.WorkspaceStatusStarting, + CreatedAt: now, + }, + expectCurrentState: true, + expectedTimestamp: now, + expectedState: codersdk.TaskStateWorking, + expectedMessage: "Workspace is starting", + }, + { + name: "TaskInitializing_AgentConnecting", + task: database.Task{ + ID: uuid.New(), + Status: database.TaskStatusInitializing, + }, + agentLifecycle: ptr.Ref(codersdk.WorkspaceAgentLifecycleCreated), + appHealth: nil, + latestAppStatus: nil, + latestBuild: codersdk.WorkspaceBuild{ + Status: codersdk.WorkspaceStatusRunning, + CreatedAt: now, + }, + expectCurrentState: true, + expectedTimestamp: now, + expectedState: codersdk.TaskStateWorking, + expectedMessage: "Agent is connecting", + }, + { + name: "TaskInitializing_AgentStarting", + task: database.Task{ + ID: uuid.New(), + Status: database.TaskStatusInitializing, + }, + agentLifecycle: ptr.Ref(codersdk.WorkspaceAgentLifecycleStarting), + appHealth: nil, + latestAppStatus: nil, + latestBuild: codersdk.WorkspaceBuild{ + Status: codersdk.WorkspaceStatusRunning, + CreatedAt: now, + }, + expectCurrentState: true, + expectedTimestamp: now, + expectedState: codersdk.TaskStateWorking, + expectedMessage: "Agent is starting", + }, + { + name: "TaskInitializing_AppInitializing", + task: database.Task{ + ID: uuid.New(), + Status: database.TaskStatusInitializing, + }, + agentLifecycle: ptr.Ref(codersdk.WorkspaceAgentLifecycleReady), + appHealth: ptr.Ref(codersdk.WorkspaceAppHealthInitializing), + latestAppStatus: nil, + latestBuild: codersdk.WorkspaceBuild{ + Status: codersdk.WorkspaceStatusRunning, + CreatedAt: now, + }, + expectCurrentState: true, + expectedTimestamp: now, + expectedState: codersdk.TaskStateWorking, + expectedMessage: "App is initializing", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ws := codersdk.Workspace{ + LatestBuild: tt.latestBuild, + LatestAppStatus: tt.latestAppStatus, + } + + currentState := deriveTaskCurrentState(tt.task, ws, tt.agentLifecycle, tt.appHealth) + + if tt.expectCurrentState { + require.NotNil(t, currentState) + assert.Equal(t, tt.expectedTimestamp.UTC(), currentState.Timestamp.UTC()) + assert.Equal(t, tt.expectedState, currentState.State) + assert.Equal(t, tt.expectedMessage, currentState.Message) + } else { + assert.Nil(t, currentState) + } + }) + } +} diff --git a/coderd/aitasks_test.go b/coderd/aitasks_test.go index eaac973b159e6..8582765c01174 100644 --- a/coderd/aitasks_test.go +++ b/coderd/aitasks_test.go @@ -240,14 +240,18 @@ func TestTasks(t *testing.T) { assert.NotNil(t, updated.CurrentState, "current state should not be nil") assert.Equal(t, "all done", updated.CurrentState.Message) assert.Equal(t, codersdk.TaskStateComplete, updated.CurrentState.State) + previousCurrentState := updated.CurrentState // Start the workspace again coderdtest.MustTransitionWorkspace(t, client, task.WorkspaceID.UUID, codersdk.WorkspaceTransitionStop, codersdk.WorkspaceTransitionStart) - // Verify that the status from the previous build is no longer present + // Verify that the status from the previous build has been cleared + // and replaced by the agent initialization status. updated, err = exp.TaskByID(ctx, task.ID) require.NoError(t, err) - assert.Nil(t, updated.CurrentState, "current state should be nil") + assert.NotEqual(t, previousCurrentState, updated.CurrentState) + assert.Equal(t, codersdk.TaskStateWorking, updated.CurrentState.State) + assert.NotEqual(t, "all done", updated.CurrentState.Message) }) t.Run("Delete", func(t *testing.T) { diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index ba430cd57e7ca..a682ec838ffed 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -361,12 +361,20 @@ func (b WorkspaceBuildBuilder) doInTX() WorkspaceResponse { require.Fail(b.t, "task app not configured but workspace is a task workspace") } - app := mustWorkspaceAppByWorkspaceAndBuildAndAppID(ownerCtx, b.t, b.db, resp.Workspace.ID, resp.Build.BuildNumber, b.taskAppID) + workspaceAgentID := uuid.NullUUID{} + workspaceAppID := uuid.NullUUID{} + // Workspace agent and app are only properly set upon job completion + if b.jobStatus != database.ProvisionerJobStatusPending && b.jobStatus != database.ProvisionerJobStatusRunning { + app := mustWorkspaceAppByWorkspaceAndBuildAndAppID(ownerCtx, b.t, b.db, resp.Workspace.ID, resp.Build.BuildNumber, b.taskAppID) + workspaceAgentID = uuid.NullUUID{UUID: app.AgentID, Valid: true} + workspaceAppID = uuid.NullUUID{UUID: app.ID, Valid: true} + } + _, err = b.db.UpsertTaskWorkspaceApp(ownerCtx, database.UpsertTaskWorkspaceAppParams{ TaskID: task.ID, WorkspaceBuildNumber: resp.Build.BuildNumber, - WorkspaceAgentID: uuid.NullUUID{UUID: app.AgentID, Valid: true}, - WorkspaceAppID: uuid.NullUUID{UUID: app.ID, Valid: true}, + WorkspaceAgentID: workspaceAgentID, + WorkspaceAppID: workspaceAppID, }) require.NoError(b.t, err, "upsert task workspace app") b.logger.Debug(context.Background(), "linked task to workspace build", From a2728439ffe37f481ceb8642186240ee5abb4898 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 17 Nov 2025 11:34:01 -0600 Subject: [PATCH 161/255] docs: add API key scopes documentation (#20742) ## Description Adds a brief section to the API & Session Tokens documentation explaining API key scopes. ## Changes - Added "API Key Scopes" section to `docs/admin/users/sessions-tokens.md` - Includes overview of scope functionality and security benefits - Documents scope format (`resource:action`) and wildcard usage - Provides CLI examples for creating scoped tokens - Lists common scope examples with descriptions ## Motivation Users need documentation on how to create and use scoped API tokens for improved security by limiting token permissions to only necessary operations. ## Testing - Reviewed documentation formatting - Verified markdown structure - Confirmed examples are accurate --- docs/admin/users/sessions-tokens.md | 51 +++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/docs/admin/users/sessions-tokens.md b/docs/admin/users/sessions-tokens.md index 8152c92290877..901f4ae038cd3 100644 --- a/docs/admin/users/sessions-tokens.md +++ b/docs/admin/users/sessions-tokens.md @@ -80,3 +80,54 @@ You can use the [`CODER_MAX_TOKEN_LIFETIME`](https://coder.com/docs/reference/cli/server#--max-token-lifetime) server flag to set the maximum duration for long-lived tokens in your deployment. + +## API Key Scopes + +API key scopes allow you to limit the permissions of a token to specific operations. By default, tokens are created with the `all` scope, granting full access to all actions the user can perform. For improved security, you can create tokens with limited scopes that restrict access to only the operations needed. + +Scopes follow the format `resource:action`, where `resource` is the type of object (like `workspace`, `template`, or `user`) and `action` is the operation (like `read`, `create`, `update`, or `delete`). You can also use wildcards like `workspace:*` to grant all permissions for a specific resource type. + +### Creating tokens with scopes + +You can specify scopes when creating a token using the `--scope` flag: + +```sh +# Create a token that can only read workspaces +coder tokens create --name "readonly-token" --scope "workspace:read" + +# Create a token with multiple scopes +coder tokens create --name "limited-token" --scope "workspace:read" --scope "template:read" +``` + +Common scope examples include: + +- `workspace:read` - View workspace information +- `workspace:*` - Full workspace access (create, read, update, delete) +- `template:read` - View template information +- `api_key:read` - View API keys (useful for automation) +- `application_connect` - Connect to workspace applications + +For a complete list of available scopes, see the API reference documentation. + +### Allow lists (advanced) + +For additional security, you can combine scopes with allow lists to restrict tokens to specific resources. Allow lists let you limit a token to only interact with particular workspaces, templates, or other resources by their UUID: + +```sh +# Create a token limited to a specific workspace +coder tokens create --name "workspace-token" \ + --scope "workspace:read" \ + --allow "workspace:a1b2c3d4-5678-90ab-cdef-1234567890ab" +``` + +**Important:** Allow lists are exclusive - the token can **only** perform actions on resources explicitly listed. In the example above, the token can only read the specified workspace and cannot access any other resources (templates, organizations, other workspaces, etc.). To maintain access to other resources, you must explicitly add them to the allow list: + +```sh +# Token that can read one workspace AND access templates and user info +coder tokens create --name "limited-token" \ + --scope "workspace:read" --scope "template:*" --scope "user:read" \ + --allow "workspace:a1b2c3d4-5678-90ab-cdef-1234567890ab" \ + --allow "template:*" \ + --allow "user:*" \ + ... etc +``` From a83328c1f08c2c909bfdc33f15a9aa6242ce2056 Mon Sep 17 00:00:00 2001 From: Yevhenii Shcherbina Date: Mon, 17 Nov 2025 15:41:59 -0500 Subject: [PATCH 162/255] docs: improve boundary docs (#20806) Update docs for boundary v0.2.0 release. --- docs/ai-coder/agent-boundary.md | 76 ++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/docs/ai-coder/agent-boundary.md b/docs/ai-coder/agent-boundary.md index 36e36a08b6d2f..5944cfbf2b1f3 100644 --- a/docs/ai-coder/agent-boundary.md +++ b/docs/ai-coder/agent-boundary.md @@ -16,32 +16,86 @@ Agent Boundaries offer network policy enforcement, which blocks domains and HTTP The easiest way to use Agent Boundaries is through existing Coder modules, such as the [Claude Code module](https://registry.coder.com/modules/coder/claude-code). It can also be ran directly in the terminal by installing the [CLI](https://github.com/coder/boundary). -Below is an example of how to configure Agent Boundaries for usage in your workspace. +There are two supported ways to configure Boundary today: + +1. **Inline module configuration** – fastest for quick testing. +2. **External `config.yaml`** – best when you need a large allow list or want everyone who launches Boundary manually to share the same config. + +### Option 1: Inline module configuration (quick start) + +Put every setting directly in the Terraform module when you just want to experiment: ```tf module "claude-code" { source = "dev.registry.coder.com/coder/claude-code/coder" + version = "4.1.0" enable_boundary = true - boundary_version = "main" - boundary_log_dir = "/tmp/boundary_logs" + boundary_version = "v0.2.0" + boundary_log_dir = "/tmp/boundary_logs" boundary_log_level = "WARN" - boundary_additional_allowed_urls = ["GET *google.com"] + boundary_additional_allowed_urls = ["domain=google.com"] boundary_proxy_port = "8087" - version = "3.2.1" } ``` -- `boundary_version` defines what version of Boundary is being applied. This is set to `main`, which points to the main branch of `coder/boundary`. +All Boundary knobs live in Terraform, so you can iterate quickly without creating extra files. + +### Option 2: Keep policy in `config.yaml` (extensive allow lists) + +When you need to maintain a long allow list or share a detailed policy with teammates, keep Terraform minimal and move the rest into `config.yaml`: + +```tf +module "claude-code" { + source = "dev.registry.coder.com/coder/claude-code/coder" + version = "4.1.0" + enable_boundary = true + boundary_version = "v0.2.0" +} +``` + +Then create a `config.yaml` file in your template directory with your policy: + +```yaml +allowlist: + - "domain=google.com" + - "method=GET,HEAD domain=api.github.com" + - "method=POST domain=api.example.com path=/users,/posts" +log_dir: /tmp/boundary_logs +proxy_port: 8087 +log_level: warn +``` + +Add a `coder_script` resource to mount the configuration file into the workspace filesystem: + +```tf +resource "coder_script" "boundary_config_setup" { + agent_id = coder_agent.dev.id + display_name = "Boundary Setup Configuration" + run_on_start = true + + script = <<-EOF + #!/bin/sh + mkdir -p ~/.config/coder_boundary + echo '${base64encode(file("${path.module}/config.yaml"))}' | base64 -d > ~/.config/coder_boundary/config.yaml + chmod 600 ~/.config/coder_boundary/config.yaml + EOF +} +``` + +Boundary automatically reads `config.yaml` from `~/.config/coder_boundary/` when it starts, so everyone who launches Boundary manually inside the workspace picks up the same configuration without extra flags. This is especially convenient for managing extensive allow lists in version control. + +- `boundary_version` defines what version of Boundary is being applied. This is set to `v0.2.0`, which points to the v0.2.0 release tag of `coder/boundary`. - `boundary_log_dir` is the directory where log files are written to when the workspace spins up. - `boundary_log_level` defines the verbosity at which requests are logged. Boundary uses the following verbosity levels: - `WARN`: logs only requests that have been blocked by Boundary - `INFO`: logs all requests at a high level - `DEBUG`: logs all requests in detail -- `boundary_additional_allowed_urls`: defines the URLs that the agent can access, in additional to the default URLs required for the agent to work - - `github.com` means only the specific domain is allowed - - `*.github.com` means only the subdomains are allowed - the specific domain is excluded - - `*github.com` means both the specific domain and all subdomains are allowed - - You can also also filter on methods, hostnames, and paths - for example, `GET,HEAD *github.com/coder`. +- `boundary_additional_allowed_urls`: defines the URLs that the agent can access, in addition to the default URLs required for the agent to work. Rules use the format `"key=value [key=value ...]"`: + - `domain=github.com` - allows the domain and all its subdomains + - `domain=*.github.com` - allows only subdomains (the specific domain is excluded) + - `method=GET,HEAD domain=api.github.com` - allows specific HTTP methods for a domain + - `method=POST domain=api.example.com path=/users,/posts` - allows specific methods, domain, and paths + - `path=/api/v1/*,/api/v2/*` - allows specific URL paths You can also run Agent Boundaries directly in your workspace and configure it per template. You can do so by installing the [binary](https://github.com/coder/boundary) into the workspace image or at start-up. You can do so with the following command: From eb644732d75a815d248baf693ad0e9da02d4ae12 Mon Sep 17 00:00:00 2001 From: Yevhenii Shcherbina Date: Mon, 17 Nov 2025 16:59:24 -0500 Subject: [PATCH 163/255] feat: enable boundary on dogfood (#20766) Enable boundary on dogfood. Allowed domains are specified in config.yaml file. --- dogfood/coder/boundary-config.yaml | 222 +++++++++++++++++++++++++++++ dogfood/coder/main.tf | 17 ++- 2 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 dogfood/coder/boundary-config.yaml diff --git a/dogfood/coder/boundary-config.yaml b/dogfood/coder/boundary-config.yaml new file mode 100644 index 0000000000000..2b6c412a36ab4 --- /dev/null +++ b/dogfood/coder/boundary-config.yaml @@ -0,0 +1,222 @@ +allowlist: + # specified in claude-code module as well (effectively a duplicate); needed for basic functionality of claude-code agent + - domain=anthropic.com + - domain=registry.npmjs.org + - domain=sentry.io + - domain=claude.ai + - domain=dev.coder.com + + # test domains + - method=GET domain=google.com + - method=GET domain=typicode.com + + # domain used in coder task workspaces + - method=POST domain=http-intake.logs.datadoghq.com + + # Default allowed domains from Claude Code on the web + # Source: https://code.claude.com/docs/en/claude-code-on-the-web#default-allowed-domains + # Anthropic Services + - domain=api.anthropic.com + - domain=statsig.anthropic.com + - domain=claude.ai + + # Version Control + - domain=github.com + - domain=www.github.com + - domain=api.github.com + - domain=raw.githubusercontent.com + - domain=objects.githubusercontent.com + - domain=codeload.github.com + - domain=avatars.githubusercontent.com + - domain=camo.githubusercontent.com + - domain=gist.github.com + - domain=gitlab.com + - domain=www.gitlab.com + - domain=registry.gitlab.com + - domain=bitbucket.org + - domain=www.bitbucket.org + - domain=api.bitbucket.org + + # Container Registries + - domain=registry-1.docker.io + - domain=auth.docker.io + - domain=index.docker.io + - domain=hub.docker.com + - domain=www.docker.com + - domain=production.cloudflare.docker.com + - domain=download.docker.com + - domain=*.gcr.io + - domain=ghcr.io + - domain=mcr.microsoft.com + - domain=*.data.mcr.microsoft.com + + # Cloud Platforms + - domain=cloud.google.com + - domain=accounts.google.com + - domain=gcloud.google.com + - domain=*.googleapis.com + - domain=storage.googleapis.com + - domain=compute.googleapis.com + - domain=container.googleapis.com + - domain=azure.com + - domain=portal.azure.com + - domain=microsoft.com + - domain=www.microsoft.com + - domain=*.microsoftonline.com + - domain=packages.microsoft.com + - domain=dotnet.microsoft.com + - domain=dot.net + - domain=visualstudio.com + - domain=dev.azure.com + - domain=oracle.com + - domain=www.oracle.com + - domain=java.com + - domain=www.java.com + - domain=java.net + - domain=www.java.net + - domain=download.oracle.com + - domain=yum.oracle.com + + # Package Managers - JavaScript/Node + - domain=registry.npmjs.org + - domain=www.npmjs.com + - domain=www.npmjs.org + - domain=npmjs.com + - domain=npmjs.org + - domain=yarnpkg.com + - domain=registry.yarnpkg.com + + # Package Managers - Python + - domain=pypi.org + - domain=www.pypi.org + - domain=files.pythonhosted.org + - domain=pythonhosted.org + - domain=test.pypi.org + - domain=pypi.python.org + - domain=pypa.io + - domain=www.pypa.io + + # Package Managers - Ruby + - domain=rubygems.org + - domain=www.rubygems.org + - domain=api.rubygems.org + - domain=index.rubygems.org + - domain=ruby-lang.org + - domain=www.ruby-lang.org + - domain=rubyforge.org + - domain=www.rubyforge.org + - domain=rubyonrails.org + - domain=www.rubyonrails.org + - domain=rvm.io + - domain=get.rvm.io + + # Package Managers - Rust + - domain=crates.io + - domain=www.crates.io + - domain=static.crates.io + - domain=rustup.rs + - domain=static.rust-lang.org + - domain=www.rust-lang.org + + # Package Managers - Go + - domain=proxy.golang.org + - domain=sum.golang.org + - domain=index.golang.org + - domain=golang.org + - domain=www.golang.org + - domain=goproxy.io + - domain=pkg.go.dev + + # Package Managers - JVM + - domain=maven.org + - domain=repo.maven.org + - domain=central.maven.org + - domain=repo1.maven.org + - domain=jcenter.bintray.com + - domain=gradle.org + - domain=www.gradle.org + - domain=services.gradle.org + - domain=spring.io + - domain=repo.spring.io + + # Package Managers - Other Languages + - domain=packagist.org + - domain=www.packagist.org + - domain=repo.packagist.org + - domain=nuget.org + - domain=www.nuget.org + - domain=api.nuget.org + - domain=pub.dev + - domain=api.pub.dev + - domain=hex.pm + - domain=www.hex.pm + - domain=cpan.org + - domain=www.cpan.org + - domain=metacpan.org + - domain=www.metacpan.org + - domain=api.metacpan.org + - domain=cocoapods.org + - domain=www.cocoapods.org + - domain=cdn.cocoapods.org + - domain=haskell.org + - domain=www.haskell.org + - domain=hackage.haskell.org + - domain=swift.org + - domain=www.swift.org + + # Linux Distributions + - domain=archive.ubuntu.com + - domain=security.ubuntu.com + - domain=ubuntu.com + - domain=www.ubuntu.com + - domain=*.ubuntu.com + - domain=ppa.launchpad.net + - domain=launchpad.net + - domain=www.launchpad.net + + # Development Tools & Platforms + - domain=dl.k8s.io + - domain=pkgs.k8s.io + - domain=k8s.io + - domain=www.k8s.io + - domain=releases.hashicorp.com + - domain=apt.releases.hashicorp.com + - domain=rpm.releases.hashicorp.com + - domain=archive.releases.hashicorp.com + - domain=hashicorp.com + - domain=www.hashicorp.com + - domain=repo.anaconda.com + - domain=conda.anaconda.org + - domain=anaconda.org + - domain=www.anaconda.com + - domain=anaconda.com + - domain=continuum.io + - domain=apache.org + - domain=www.apache.org + - domain=archive.apache.org + - domain=downloads.apache.org + - domain=eclipse.org + - domain=www.eclipse.org + - domain=download.eclipse.org + - domain=nodejs.org + - domain=www.nodejs.org + + # Cloud Services & Monitoring + - domain=statsig.com + - domain=www.statsig.com + - domain=api.statsig.com + - domain=*.sentry.io + + # Content Delivery & Mirrors + - domain=*.sourceforge.net + - domain=packagecloud.io + - domain=*.packagecloud.io + + # Schema & Configuration + - domain=json-schema.org + - domain=www.json-schema.org + - domain=json.schemastore.org + - domain=www.schemastore.org +log_dir: /tmp/boundary_logs +log_level: warn +proxy_port: 8087 diff --git a/dogfood/coder/main.tf b/dogfood/coder/main.tf index 88a869dbad0ec..cb4eb09e71a79 100644 --- a/dogfood/coder/main.tf +++ b/dogfood/coder/main.tf @@ -844,10 +844,25 @@ locals { EOT } +resource "coder_script" "boundary_config_setup" { + agent_id = coder_agent.dev.id + display_name = "Boundary Setup Configuration" + run_on_start = true + + script = <<-EOF + #!/bin/sh + mkdir -p ~/.config/coder_boundary + echo '${base64encode(file("${path.module}/boundary-config.yaml"))}' | base64 -d > ~/.config/coder_boundary/config.yaml + chmod 600 ~/.config/coder_boundary/config.yaml + EOF +} + module "claude-code" { count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0 source = "dev.registry.coder.com/coder/claude-code/coder" - version = "4.0.0" + version = "4.1.0" + enable_boundary = true + boundary_version = "v0.2.0" agent_id = coder_agent.dev.id workdir = local.repo_dir claude_code_version = "latest" From 158243d1464657442c1f7f8275190344adfeae12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Banaszewski?= Date: Tue, 18 Nov 2025 09:52:44 +0100 Subject: [PATCH 164/255] fix: add cache for terraform installer files (#20776) Replaces not working mocks by simple proxy that caches terraform files using test cache https://github.com/coder/coder/blob/16b8e6072fd84f45404e3f84bb2b6fea2424b090/testutil/cache.go#L13 Fixes: https://github.com/coder/internal/issues/1126 --- provisioner/terraform/install.go | 9 +- provisioner/terraform/install_test.go | 220 +++++++++----------------- provisioner/terraform/serve.go | 2 +- 3 files changed, 77 insertions(+), 154 deletions(-) diff --git a/provisioner/terraform/install.go b/provisioner/terraform/install.go index 19bf7c6a8dfe4..83791abfc11a6 100644 --- a/provisioner/terraform/install.go +++ b/provisioner/terraform/install.go @@ -34,7 +34,7 @@ var ( // operation. // //nolint:revive // verbose is a control flag that controls the verbosity of the log output. -func Install(ctx context.Context, log slog.Logger, verbose bool, dir string, wantVersion *version.Version, baseUrl string, verifyChecksums bool) (string, error) { +func Install(ctx context.Context, log slog.Logger, verbose bool, dir string, wantVersion *version.Version, baseUrl string) (string, error) { err := os.MkdirAll(dir, 0o750) if err != nil { return "", err @@ -63,10 +63,9 @@ func Install(ctx context.Context, log slog.Logger, verbose bool, dir string, wan } installer := &releases.ExactVersion{ - InstallDir: dir, - Product: product.Terraform, - Version: TerraformVersion, - SkipChecksumVerification: !verifyChecksums, + InstallDir: dir, + Product: product.Terraform, + Version: TerraformVersion, } installer.SetLogger(slog.Stdlib(ctx, log, slog.LevelDebug)) if baseUrl != "" { diff --git a/provisioner/terraform/install_test.go b/provisioner/terraform/install_test.go index edbc758043b77..c259ccd2d2ebc 100644 --- a/provisioner/terraform/install_test.go +++ b/provisioner/terraform/install_test.go @@ -6,12 +6,12 @@ package terraform_test import ( - "archive/zip" "context" - "encoding/json" - "fmt" + "errors" + "io" "net" "net/http" + "net/url" "os" "path/filepath" "strings" @@ -28,17 +28,8 @@ import ( ) const ( - // simple script that mocks `./terraform version -json` - terraformExecutableTemplate = `#!/bin/bash -cat < zip contains 'terraform' binary and sometimes 'LICENSE.txt' -func createFakeTerraformInstallationFiles(t *testing.T) string { - tmpDir := t.TempDir() - - mij := mustMarshal(t, mainJSON(version1, version2)) - jv1 := mustMarshal(t, versionedJSON(version1)) - jv2 := mustMarshal(t, versionedJSON(version2)) - - // `index.json` - require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "index.json"), mij, 0o400)) - - // `${version1}/index.json` - require.NoError(t, os.Mkdir(filepath.Join(tmpDir, version1.String()), 0o700)) - require.NoError(t, os.WriteFile(filepath.Join(tmpDir, version1.String(), "index.json"), jv1, 0o400)) - - // `${version2}/index.json` - require.NoError(t, os.Mkdir(filepath.Join(tmpDir, version2.String()), 0o700)) - require.NoError(t, os.WriteFile(filepath.Join(tmpDir, version2.String(), "index.json"), jv2, 0o400)) - - // `${version1}/linux_amd64.zip` - zip1, err := os.Create(filepath.Join(tmpDir, version1.String(), zipFilename(version1))) - require.NoError(t, err) - zip1Writer := zip.NewWriter(zip1) +func uriToFilename(u url.URL) string { + return strings.ReplaceAll(u.RequestURI(), "/", "_") +} - // `${version1}/linux_amd64.zip/terraform` - exe1, err := zip1Writer.Create("terraform") - require.NoError(t, err) - n, err := exe1.Write(exeContent(version1)) - require.NoError(t, err) - require.NotZero(t, n) +func (p *terraformProxy) handleGet(w http.ResponseWriter, r *http.Request) { + p.mutex.Lock() + defer p.mutex.Unlock() - // `${version1}/linux_amd64.zip/LICENSE.txt` - lic1, err := zip1Writer.Create("LICENSE.txt") - require.NoError(t, err) - n, err = lic1.Write([]byte("some license")) - require.NoError(t, err) - require.NotZero(t, n) - require.NoError(t, zip1Writer.Close()) + filename := uriToFilename(*r.URL) + path := filepath.Join(p.cacheRoot, filename) + if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { + require.NoError(p.t, os.MkdirAll(p.cacheRoot, os.ModeDir|0o700)) - // `${version2}/linux_amd64.zip` - zip2, err := os.Create(filepath.Join(tmpDir, version2.String(), zipFilename(version2))) - require.NoError(t, err) - zip2Writer := zip.NewWriter(zip2) + // Update cache + req, err := http.NewRequestWithContext(p.t.Context(), "GET", terraformURL+r.URL.Path, nil) + require.NoError(p.t, err) - // `${version1}/linux_amd64.zip/terraform` - exe2, err := zip2Writer.Create("terraform") - require.NoError(t, err) - n, err = exe2.Write(exeContent(version2)) - require.NoError(t, err) - require.NotZero(t, n) - require.NoError(t, zip2Writer.Close()) + resp, err := p.httpClient.Do(req) + require.NoError(p.t, err) + defer resp.Body.Close() - return tmpDir -} + body, err := io.ReadAll(resp.Body) + require.NoError(p.t, err) -// starts http server serving fake terraform installation files -func startFakeTerraformServer(t *testing.T, tmpDir string) string { - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("failed to create listener") + // update index.json so urls in it point to proxy by making them relative + // "https://releases.hashicorp.com/terraform/1.13.4/terraform_1.13.4_windows_amd64.zip" -> "/terraform/1.13.4/terraform_1.13.4_windows_amd64.zip" + if strings.HasSuffix(r.URL.Path, "index.json") { + body = []byte(strings.ReplaceAll(string(body), terraformURL, "")) + } + require.NoError(p.t, os.WriteFile(path, body, 0o400)) + } else if err != nil { + p.t.Errorf("unexpected error when trying to read file from cache: %v", err) } - mux := http.NewServeMux() - fs := http.FileServer(http.Dir(tmpDir)) - mux.Handle("/terraform/", http.StripPrefix("/terraform", fs)) - - srv := http.Server{ - ReadHeaderTimeout: time.Second, - Handler: mux, - } - go srv.Serve(listener) - t.Cleanup(func() { - if err := srv.Close(); err != nil { - t.Errorf("failed to close server: %v", err) - } - }) - return "http://" + listener.Addr().String() + // Serve from cache + r.URL.Path = filename + r.URL.RawPath = filename + p.fsHandler.ServeHTTP(w, r) } func TestInstall(t *testing.T) { @@ -205,8 +126,11 @@ func TestInstall(t *testing.T) { dir := t.TempDir() log := testutil.Logger(t) - tmpDir := createFakeTerraformInstallationFiles(t) - addr := startFakeTerraformServer(t, tmpDir) + proxy := persistentlyCachedProxy(t) + go proxy.srv.Serve(proxy.listener) + t.Cleanup(func() { + require.NoError(t, proxy.srv.Close()) + }) // Install spins off 8 installs with Version and waits for them all // to complete. The locking mechanism within Install should @@ -219,7 +143,7 @@ func TestInstall(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - p, err := terraform.Install(ctx, log, false, dir, version, addr, false) + p, err := terraform.Install(ctx, log, false, dir, version, "http://"+proxy.listener.Addr().String()) assert.NoError(t, err) paths <- p }() diff --git a/provisioner/terraform/serve.go b/provisioner/terraform/serve.go index a927f288fae81..32b5343f6f3ce 100644 --- a/provisioner/terraform/serve.go +++ b/provisioner/terraform/serve.go @@ -103,7 +103,7 @@ func Serve(ctx context.Context, options *ServeOptions) error { slog.F("min_version", minTerraformVersion.String())) } - binPath, err := Install(ctx, options.Logger, options.ExternalProvisioner, options.CachePath, TerraformVersion, "", true) + binPath, err := Install(ctx, options.Logger, options.ExternalProvisioner, options.CachePath, TerraformVersion, "") if err != nil { return xerrors.Errorf("install terraform: %w", err) } From c12bba40ade6aca32fb3173201396533f16d5cd2 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Wed, 19 Nov 2025 02:09:16 +1100 Subject: [PATCH 165/255] fix(enterprise/cli): preserve actual error when getting provisioner key details (#20813) Fixes #20781 Previously, when `GetProvisionerKey()` failed, the actual error was swallowed: ``` error: unable to get provisioner key details ``` Now the actual error is preserved using error wrapping, so users can see the real cause (e.g., 404 Not Found, connection refused, invalid key, etc.): ``` error: unable to get provisioner key details: GET https://...: 404 Not Found ``` This makes it much easier to diagnose configuration issues. --- *Generated by [mux](https://cmux.io)* --- enterprise/cli/provisionerdaemonstart.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/cli/provisionerdaemonstart.go b/enterprise/cli/provisionerdaemonstart.go index 4c0ed003a18df..b15e56d8ab385 100644 --- a/enterprise/cli/provisionerdaemonstart.go +++ b/enterprise/cli/provisionerdaemonstart.go @@ -107,7 +107,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command { if provisionerKey != "" { pkDetails, err := client.GetProvisionerKey(ctx, provisionerKey) if err != nil { - return xerrors.New("unable to get provisioner key details") + return xerrors.Errorf("unable to get provisioner key details: %w", err) } for k, v := range pkDetails.Tags { From 085370ec6d3ff92830e6403f43b18bed91451c42 Mon Sep 17 00:00:00 2001 From: AlexanderSarson <40232816+AlexanderSarson@users.noreply.github.com> Date: Wed, 19 Nov 2025 04:11:56 +0100 Subject: [PATCH 166/255] chore: add "positron:" to allowed external app protocols (#20803) --- site/src/modules/apps/apps.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/modules/apps/apps.ts b/site/src/modules/apps/apps.ts index 2576422ea46ab..3f1cfecea784f 100644 --- a/site/src/modules/apps/apps.ts +++ b/site/src/modules/apps/apps.ts @@ -23,6 +23,7 @@ const ALLOWED_EXTERNAL_APP_PROTOCOLS = [ "jetbrains-gateway:", "jetbrains:", "kiro:", + "positron:", ]; type GetVSCodeHrefParams = { From 52f8143ad326a899da01ebc7b9a84371402d09ef Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Wed, 19 Nov 2025 16:47:06 +1100 Subject: [PATCH 167/255] fix: rename `AI Governance` to `AI Bridge` (#20790) This pull-request simply renames our `AI Governance` feature to `AI Bridge` whilst we evaluate the future of how we want to render the governance of AI related features. --- .../dashboard/Navbar/DeploymentDropdown.tsx | 18 +++++++----------- site/src/modules/dashboard/Navbar/Navbar.tsx | 4 ++-- .../modules/dashboard/Navbar/NavbarView.tsx | 6 +++--- .../AIBridgeHelpTooltip.tsx} | 6 +++--- .../AIBridgeLayout.tsx} | 10 +++++----- .../RequestLogsPage/RequestLogsPage.tsx | 2 +- .../RequestLogsPageView.stories.tsx | 2 +- .../RequestLogsPage/RequestLogsPageView.tsx | 4 ++-- .../RequestLogsRow/RequestLogsRow.stories.tsx | 2 +- .../RequestLogsRow/RequestLogsRow.tsx | 0 .../filter/RequestLogsFilter.tsx | 0 .../RequestLogsPage/filter/filter.tsx | 0 site/src/router.tsx | 15 ++++++--------- 13 files changed, 31 insertions(+), 38 deletions(-) rename site/src/pages/{AIGovernancePage/AIGovernanceHelpTooltip.tsx => AIBridgePage/AIBridgeHelpTooltip.tsx} (76%) rename site/src/pages/{AIGovernancePage/AIGovernanceLayout.tsx => AIBridgePage/AIBridgeLayout.tsx} (70%) rename site/src/pages/{AIGovernancePage => AIBridgePage}/RequestLogsPage/RequestLogsPage.tsx (96%) rename site/src/pages/{AIGovernancePage => AIBridgePage}/RequestLogsPage/RequestLogsPageView.stories.tsx (96%) rename site/src/pages/{AIGovernancePage => AIBridgePage}/RequestLogsPage/RequestLogsPageView.tsx (92%) rename site/src/pages/{AIGovernancePage => AIBridgePage}/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx (91%) rename site/src/pages/{AIGovernancePage => AIBridgePage}/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx (100%) rename site/src/pages/{AIGovernancePage => AIBridgePage}/RequestLogsPage/filter/RequestLogsFilter.tsx (100%) rename site/src/pages/{AIGovernancePage => AIBridgePage}/RequestLogsPage/filter/filter.tsx (100%) diff --git a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx index 53e498e1bea21..fc7825b3dfc7f 100644 --- a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx +++ b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx @@ -18,7 +18,7 @@ interface DeploymentDropdownProps { canViewAuditLog: boolean; canViewConnectionLog: boolean; canViewHealth: boolean; - canViewAIGovernance: boolean; + canViewAIBridge: boolean; } export const DeploymentDropdown: FC = ({ @@ -27,7 +27,7 @@ export const DeploymentDropdown: FC = ({ canViewAuditLog, canViewConnectionLog, canViewHealth, - canViewAIGovernance, + canViewAIBridge, }) => { if ( !canViewAuditLog && @@ -58,7 +58,7 @@ export const DeploymentDropdown: FC = ({ canViewAuditLog={canViewAuditLog} canViewConnectionLog={canViewConnectionLog} canViewHealth={canViewHealth} - canViewAIGovernance={canViewAIGovernance} + canViewAIBridge={canViewAIBridge} /> @@ -71,7 +71,7 @@ const DeploymentDropdownContent: FC = ({ canViewAuditLog, canViewHealth, canViewConnectionLog, - canViewAIGovernance, + canViewAIBridge, }) => { return (
Release notes

Sourced from google.golang.org/api's releases.

v0.256.0

0.256.0 (2025-11-10)

Features

diff --git a/site/src/pages/AIGovernancePage/AIGovernanceHelpTooltip.tsx b/site/src/pages/AIBridgePage/AIBridgeHelpTooltip.tsx similarity index 76% rename from site/src/pages/AIGovernancePage/AIGovernanceHelpTooltip.tsx rename to site/src/pages/AIBridgePage/AIBridgeHelpTooltip.tsx index e8dd2a6b77b20..d55ee84f87500 100644 --- a/site/src/pages/AIGovernancePage/AIGovernanceHelpTooltip.tsx +++ b/site/src/pages/AIBridgePage/AIBridgeHelpTooltip.tsx @@ -10,15 +10,15 @@ import { import type { FC } from "react"; import { docs } from "utils/docs"; -export const AIGovernanceHelpTooltip: FC = () => { +export const AIBridgeHelpTooltip: FC = () => { return ( - What is AI Governance? + What is AI Bridge? - AI Governance is a proxy that unifies and audits LLM usage across your + AI Bridge is a proxy that unifies and audits LLM usage across your organization. diff --git a/site/src/pages/AIGovernancePage/AIGovernanceLayout.tsx b/site/src/pages/AIBridgePage/AIBridgeLayout.tsx similarity index 70% rename from site/src/pages/AIGovernancePage/AIGovernanceLayout.tsx rename to site/src/pages/AIBridgePage/AIBridgeLayout.tsx index bf8a0e6544b0f..f424f1a21626b 100644 --- a/site/src/pages/AIGovernancePage/AIGovernanceLayout.tsx +++ b/site/src/pages/AIBridgePage/AIBridgeLayout.tsx @@ -6,16 +6,16 @@ import { } from "components/PageHeader/PageHeader"; import type { FC, PropsWithChildren } from "react"; import { Outlet } from "react-router"; -import { AIGovernanceHelpTooltip } from "./AIGovernanceHelpTooltip"; +import { AIBridgeHelpTooltip } from "./AIBridgeHelpTooltip"; -const AIGovernanceLayout: FC = () => { +const AIBridgeLayout: FC = () => { return (
- AI Governance - + AI Bridge +
@@ -27,4 +27,4 @@ const AIGovernanceLayout: FC = () => { ); }; -export default AIGovernanceLayout; +export default AIBridgeLayout; diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPage.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPage.tsx similarity index 96% rename from site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPage.tsx rename to site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPage.tsx index 2f56ff476091a..07d48e2c26021 100644 --- a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPage.tsx +++ b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPage.tsx @@ -43,7 +43,7 @@ const RequestLogsPage: FC = () => { return ( <> - {pageTitle("Request Logs", "AI Governance")} + {pageTitle("Request Logs", "AI Bridge")} ({ const interceptions = [MockInterception, MockInterception, MockInterception]; const meta: Meta = { - title: "pages/AIGovernancePage/RequestLogsPageView", + title: "pages/AIBridgePage/RequestLogsPageView", component: RequestLogsPageView, args: {}, }; diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPageView.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPageView.tsx similarity index 92% rename from site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPageView.tsx rename to site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPageView.tsx index 8592206bcdf33..fbac613fb21bb 100644 --- a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsPageView.tsx +++ b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsPageView.tsx @@ -36,8 +36,8 @@ export const RequestLogsPageView: FC = ({ if (!isRequestLogsVisible) { return ( ); diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx similarity index 91% rename from site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx rename to site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx index b4067ee3c9cb3..19ea0bec077ce 100644 --- a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx +++ b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.stories.tsx @@ -4,7 +4,7 @@ import { Table, TableBody } from "components/Table/Table"; import { RequestLogsRow } from "./RequestLogsRow"; const meta: Meta = { - title: "pages/AIGovernancePage/RequestLogsRow", + title: "pages/AIBridgePage/RequestLogsRow", component: RequestLogsRow, decorators: [ (Story) => ( diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx similarity index 100% rename from site/src/pages/AIGovernancePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx rename to site/src/pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/filter/RequestLogsFilter.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/filter/RequestLogsFilter.tsx similarity index 100% rename from site/src/pages/AIGovernancePage/RequestLogsPage/filter/RequestLogsFilter.tsx rename to site/src/pages/AIBridgePage/RequestLogsPage/filter/RequestLogsFilter.tsx diff --git a/site/src/pages/AIGovernancePage/RequestLogsPage/filter/filter.tsx b/site/src/pages/AIBridgePage/RequestLogsPage/filter/filter.tsx similarity index 100% rename from site/src/pages/AIGovernancePage/RequestLogsPage/filter/filter.tsx rename to site/src/pages/AIBridgePage/RequestLogsPage/filter/filter.tsx diff --git a/site/src/router.tsx b/site/src/router.tsx index 34b8cf82b53f4..098322b9f8369 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -334,11 +334,11 @@ const ProvisionerJobsPage = lazy( ); const TasksPage = lazy(() => import("./pages/TasksPage/TasksPage")); const TaskPage = lazy(() => import("./pages/TaskPage/TaskPage")); -const AIGovernanceLayout = lazy( - () => import("./pages/AIGovernancePage/AIGovernanceLayout"), +const AIBridgeLayout = lazy( + () => import("./pages/AIBridgePage/AIBridgeLayout"), ); -const AIGovernanceRequestLogsPage = lazy( - () => import("./pages/AIGovernancePage/RequestLogsPage/RequestLogsPage"), +const AIBridgeRequestLogsPage = lazy( + () => import("./pages/AIBridgePage/RequestLogsPage/RequestLogsPage"), ); const RoutesWithSuspense = () => { @@ -563,12 +563,9 @@ export const router = createBrowserRouter( - }> + }> } /> - } - /> + } /> }> From 5ea1353d4637c508d149940679856e62ab8bdced Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Wed, 19 Nov 2025 10:13:32 +0400 Subject: [PATCH 168/255] feat: add exp scaletest task-status command (#20761) Adds `coder exp scaletest task-status` subcommand to generate task status update load on the Coder server. --- cli/exp_scaletest.go | 1 + cli/exp_scaletest_taskstatus.go | 264 ++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 cli/exp_scaletest_taskstatus.go diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index f6fd350da23da..d8dd10da327cc 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -64,6 +64,7 @@ func (r *RootCmd) scaletestCmd() *serpent.Command { r.scaletestWorkspaceTraffic(), r.scaletestAutostart(), r.scaletestNotifications(), + r.scaletestTaskStatus(), r.scaletestSMTP(), r.scaletestPrebuilds(), }, diff --git a/cli/exp_scaletest_taskstatus.go b/cli/exp_scaletest_taskstatus.go new file mode 100644 index 0000000000000..3527c0ba3d89f --- /dev/null +++ b/cli/exp_scaletest_taskstatus.go @@ -0,0 +1,264 @@ +//go:build !slim + +package cli + +import ( + "context" + "fmt" + "net/http" + "sync" + "time" + + "github.com/google/uuid" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" + "github.com/coder/serpent" + + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/scaletest/harness" + "github.com/coder/coder/v2/scaletest/taskstatus" +) + +const ( + taskStatusTestName = "task-status" +) + +func (r *RootCmd) scaletestTaskStatus() *serpent.Command { + var ( + count int64 + template string + workspaceNamePrefix string + appSlug string + reportStatusPeriod time.Duration + reportStatusDuration time.Duration + baselineDuration time.Duration + tracingFlags = &scaletestTracingFlags{} + prometheusFlags = &scaletestPrometheusFlags{} + timeoutStrategy = &timeoutFlags{} + cleanupStrategy = newScaletestCleanupStrategy() + output = &scaletestOutputFlags{} + ) + orgContext := NewOrganizationContext() + + cmd := &serpent.Command{ + Use: "task-status", + Short: "Generates load on the Coder server by simulating task status reporting", + Long: `This test creates external workspaces and simulates AI agents reporting task status. +After all runners connect, it waits for the baseline duration before triggering status reporting.`, + Handler: func(inv *serpent.Invocation) error { + ctx := inv.Context() + + outputs, err := output.parse() + if err != nil { + return xerrors.Errorf("could not parse --output flags: %w", err) + } + + client, err := r.InitClient(inv) + if err != nil { + return err + } + + org, err := orgContext.Selected(inv, client) + if err != nil { + return err + } + + _, err = requireAdmin(ctx, client) + if err != nil { + return err + } + + // Disable rate limits for this test + client.HTTPClient = &http.Client{ + Transport: &codersdk.HeaderTransport{ + Transport: http.DefaultTransport, + Header: map[string][]string{ + codersdk.BypassRatelimitHeader: {"true"}, + }, + }, + } + + // Find the template + tpl, err := parseTemplate(ctx, client, []uuid.UUID{org.ID}, template) + if err != nil { + return xerrors.Errorf("parse template %q: %w", template, err) + } + templateID := tpl.ID + + reg := prometheus.NewRegistry() + metrics := taskstatus.NewMetrics(reg) + + logger := slog.Make(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelDebug) + prometheusSrvClose := ServeHandler(ctx, logger, promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), prometheusFlags.Address, "prometheus") + defer prometheusSrvClose() + + tracerProvider, closeTracing, tracingEnabled, err := tracingFlags.provider(ctx) + if err != nil { + return xerrors.Errorf("create tracer provider: %w", err) + } + defer func() { + // Allow time for traces to flush even if command context is + // canceled. This is a no-op if tracing is not enabled. + _, _ = fmt.Fprintln(inv.Stderr, "\nUploading traces...") + if err := closeTracing(ctx); err != nil { + _, _ = fmt.Fprintf(inv.Stderr, "\nError uploading traces: %+v\n", err) + } + // Wait for prometheus metrics to be scraped + _, _ = fmt.Fprintf(inv.Stderr, "Waiting %s for prometheus metrics to be scraped\n", prometheusFlags.Wait) + <-time.After(prometheusFlags.Wait) + }() + tracer := tracerProvider.Tracer(scaletestTracerName) + + // Setup shared resources for coordination + connectedWaitGroup := &sync.WaitGroup{} + connectedWaitGroup.Add(int(count)) + startReporting := make(chan struct{}) + + // Create the test harness + th := harness.NewTestHarness( + timeoutStrategy.wrapStrategy(harness.ConcurrentExecutionStrategy{}), + cleanupStrategy.toStrategy(), + ) + + // Create runners + for i := range count { + workspaceName := fmt.Sprintf("%s-%d", workspaceNamePrefix, i) + cfg := taskstatus.Config{ + TemplateID: templateID, + WorkspaceName: workspaceName, + AppSlug: appSlug, + ConnectedWaitGroup: connectedWaitGroup, + StartReporting: startReporting, + ReportStatusPeriod: reportStatusPeriod, + ReportStatusDuration: reportStatusDuration, + Metrics: metrics, + MetricLabelValues: []string{}, + } + + if err := cfg.Validate(); err != nil { + return xerrors.Errorf("validate config for runner %d: %w", i, err) + } + + var runner harness.Runnable = taskstatus.NewRunner(client, cfg) + if tracingEnabled { + runner = &runnableTraceWrapper{ + tracer: tracer, + spanName: fmt.Sprintf("%s/%d", taskStatusTestName, i), + runner: runner, + } + } + th.AddRun(taskStatusTestName, workspaceName, runner) + } + + // Start the test in a separate goroutine so we can coordinate timing + testCtx, testCancel := timeoutStrategy.toContext(ctx) + defer testCancel() + testDone := make(chan error) + go func() { + testDone <- th.Run(testCtx) + }() + + // Wait for all runners to connect + logger.Info(ctx, "waiting for all runners to connect") + waitCtx, waitCancel := context.WithTimeout(ctx, 5*time.Minute) + defer waitCancel() + + connectDone := make(chan struct{}) + go func() { + connectedWaitGroup.Wait() + close(connectDone) + }() + + select { + case <-waitCtx.Done(): + return xerrors.Errorf("timeout waiting for runners to connect") + case <-connectDone: + logger.Info(ctx, "all runners connected") + } + + // Wait for baseline duration + logger.Info(ctx, "waiting for baseline duration", slog.F("duration", baselineDuration)) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(baselineDuration): + } + + // Trigger all runners to start reporting + logger.Info(ctx, "triggering runners to start reporting task status") + close(startReporting) + + // Wait for the test to complete + err = <-testDone + if err != nil { + return xerrors.Errorf("run test harness: %w", err) + } + + res := th.Results() + for _, o := range outputs { + err = o.write(res, inv.Stdout) + if err != nil { + return xerrors.Errorf("write output %q to %q: %w", o.format, o.path, err) + } + } + + return nil + }, + } + + cmd.Options = serpent.OptionSet{ + { + Flag: "count", + Description: "Number of concurrent runners to create.", + Default: "10", + Value: serpent.Int64Of(&count), + }, + { + Flag: "template", + Description: "Name or UUID of the template to use for the scale test. The template MUST include a coder_external_agent and a coder_app.", + Default: "scaletest-task-status", + Value: serpent.StringOf(&template), + }, + { + Flag: "workspace-name-prefix", + Description: "Prefix for workspace names (will be suffixed with index).", + Default: "scaletest-task-status", + Value: serpent.StringOf(&workspaceNamePrefix), + }, + { + Flag: "app-slug", + Description: "Slug of the app designated as the AI Agent.", + Default: "ai-agent", + Value: serpent.StringOf(&appSlug), + }, + { + Flag: "report-status-period", + Description: "Time between reporting task statuses.", + Default: "10s", + Value: serpent.DurationOf(&reportStatusPeriod), + }, + { + Flag: "report-status-duration", + Description: "Total time to report task statuses after baseline.", + Default: "15m", + Value: serpent.DurationOf(&reportStatusDuration), + }, + { + Flag: "baseline-duration", + Description: "Duration to wait after all runners connect before starting to report status.", + Default: "10m", + Value: serpent.DurationOf(&baselineDuration), + }, + } + orgContext.AttachOptions(cmd) + output.attach(&cmd.Options) + tracingFlags.attach(&cmd.Options) + prometheusFlags.attach(&cmd.Options) + timeoutStrategy.attach(&cmd.Options) + cleanupStrategy.attach(&cmd.Options) + return cmd +} From 0bbb7dd0a3ae373184f4c451697a7d1359562fc2 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Wed, 19 Nov 2025 10:24:30 +0400 Subject: [PATCH 169/255] feat: add cleanup to task-status load test runner (#20799) Implement Cleanup in the task status Runner, to delete the external workspaces created. --- cli/exp_scaletest_taskstatus.go | 11 ++++ scaletest/taskstatus/client.go | 15 +++++ scaletest/taskstatus/run.go | 29 ++++++++- scaletest/taskstatus/run_internal_test.go | 72 +++++++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) diff --git a/cli/exp_scaletest_taskstatus.go b/cli/exp_scaletest_taskstatus.go index 3527c0ba3d89f..8621d7d2ae798 100644 --- a/cli/exp_scaletest_taskstatus.go +++ b/cli/exp_scaletest_taskstatus.go @@ -206,6 +206,17 @@ After all runners connect, it waits for the baseline duration before triggering } } + cleanupCtx, cleanupCancel := cleanupStrategy.toContext(ctx) + defer cleanupCancel() + err = th.Cleanup(cleanupCtx) + if err != nil { + return xerrors.Errorf("cleanup tests: %w", err) + } + + if res.TotalFail > 0 { + return xerrors.New("load test failed, see above for more details") + } + return nil }, } diff --git a/scaletest/taskstatus/client.go b/scaletest/taskstatus/client.go index f4271f4f31a33..badc6856f5140 100644 --- a/scaletest/taskstatus/client.go +++ b/scaletest/taskstatus/client.go @@ -34,6 +34,9 @@ type client interface { // watchWorkspace watches for updates to a workspace. watchWorkspace(ctx context.Context, workspaceID uuid.UUID) (<-chan codersdk.Workspace, error) + // deleteWorkspace deletes the workspace by creating a build with delete transition. + deleteWorkspace(ctx context.Context, workspaceID uuid.UUID) error + // initialize sets up the client with the provided logger, which is only available after Run() is called. initialize(logger slog.Logger) } @@ -101,6 +104,18 @@ func (c *sdkClient) watchWorkspace(ctx context.Context, workspaceID uuid.UUID) ( return c.coderClient.WatchWorkspace(ctx, workspaceID) } +func (c *sdkClient) deleteWorkspace(ctx context.Context, workspaceID uuid.UUID) error { + // Create a build with delete transition to delete the workspace + _, err := c.coderClient.CreateWorkspaceBuild(ctx, workspaceID, codersdk.CreateWorkspaceBuildRequest{ + Transition: codersdk.WorkspaceTransitionDelete, + Reason: codersdk.CreateWorkspaceBuildReasonCLI, + }) + if err != nil { + return xerrors.Errorf("create delete build: %w", err) + } + return nil +} + func (c *sdkClient) initialize(logger slog.Logger) { // Configure the coder client logging c.coderClient.SetLogger(logger) diff --git a/scaletest/taskstatus/run.go b/scaletest/taskstatus/run.go index 59b11237d6c9e..018ec72afedf0 100644 --- a/scaletest/taskstatus/run.go +++ b/scaletest/taskstatus/run.go @@ -41,7 +41,10 @@ type Runner struct { clock quartz.Clock } -var _ harness.Runnable = &Runner{} +var ( + _ harness.Runnable = &Runner{} + _ harness.Cleanable = &Runner{} +) // NewRunner creates a new Runner with the provided codersdk.Client and configuration. func NewRunner(coderClient *codersdk.Client, cfg Config) *Runner { @@ -111,6 +114,30 @@ func (r *Runner) Run(ctx context.Context, name string, logs io.Writer) error { return nil } +// Cleanup deletes the external workspace created by this runner. +func (r *Runner) Cleanup(ctx context.Context, id string, logs io.Writer) error { + if r.workspaceID == uuid.Nil { + // No workspace was created, nothing to cleanup + return nil + } + + logs = loadtestutil.NewSyncWriter(logs) + logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug).Named(id) + + logger.Info(ctx, "deleting external workspace", slog.F("workspace_id", r.workspaceID)) + + err := r.client.deleteWorkspace(ctx, r.workspaceID) + if err != nil { + logger.Error(ctx, "failed to delete external workspace", + slog.F("workspace_id", r.workspaceID), + slog.Error(err)) + return xerrors.Errorf("delete external workspace: %w", err) + } + + logger.Info(ctx, "successfully deleted external workspace", slog.F("workspace_id", r.workspaceID)) + return nil +} + func (r *Runner) watchWorkspaceUpdates(ctx context.Context) error { shouldMarkConnectedDone := true defer func() { diff --git a/scaletest/taskstatus/run_internal_test.go b/scaletest/taskstatus/run_internal_test.go index 3821b794aeb2c..fe5cdda04746b 100644 --- a/scaletest/taskstatus/run_internal_test.go +++ b/scaletest/taskstatus/run_internal_test.go @@ -14,6 +14,7 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" "github.com/coder/quartz" "github.com/coder/coder/v2/codersdk" @@ -57,6 +58,12 @@ func (m *fakeClient) createExternalWorkspace(ctx context.Context, req codersdk.C }, nil } +func (m *fakeClient) deleteWorkspace(ctx context.Context, workspaceID uuid.UUID) error { + m.logger.Debug(ctx, "called fake DeleteWorkspace", slog.F("workspace_id", workspaceID.String())) + // Simulate successful deletion in tests + return nil +} + // fakeAppStatusPatcher implements the appStatusPatcher interface for testing type fakeAppStatusPatcher struct { t *testing.T @@ -480,3 +487,68 @@ func TestParseStatusMessage(t *testing.T) { }) } } + +func TestRunner_Cleanup(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitMedium) + + fakeClient := &fakeClientWithCleanupTracking{ + fakeClient: newFakeClient(t), + deleteWorkspaceCalls: make([]uuid.UUID, 0), + } + fakeClient.initialize(slog.Make(sloghuman.Sink(testutil.NewTestLogWriter(t))).Leveled(slog.LevelDebug)) + + cfg := Config{ + AppSlug: "test-app", + TemplateID: uuid.UUID{5, 6, 7, 8}, + WorkspaceName: "test-workspace", + MetricLabelValues: []string{"test"}, + Metrics: NewMetrics(prometheus.NewRegistry(), "test"), + ReportStatusPeriod: 100 * time.Millisecond, + ReportStatusDuration: 200 * time.Millisecond, + StartReporting: make(chan struct{}), + ConnectedWaitGroup: &sync.WaitGroup{}, + } + + runner := &Runner{ + client: fakeClient, + patcher: newFakeAppStatusPatcher(t), + cfg: cfg, + clock: quartz.NewMock(t), + } + + logWriter := testutil.NewTestLogWriter(t) + + // Case 1: No workspace created - Cleanup should do nothing + err := runner.Cleanup(ctx, "test-runner", logWriter) + require.NoError(t, err) + require.Len(t, fakeClient.deleteWorkspaceCalls, 0, "deleteWorkspace should not be called when no workspace was created") + + // Case 2: Workspace created - Cleanup should delete it + runner.workspaceID = uuid.UUID{1, 2, 3, 4} + err = runner.Cleanup(ctx, "test-runner", logWriter) + require.NoError(t, err) + require.Len(t, fakeClient.deleteWorkspaceCalls, 1, "deleteWorkspace should be called once") + require.Equal(t, runner.workspaceID, fakeClient.deleteWorkspaceCalls[0], "deleteWorkspace should be called with correct workspace ID") + + // Case 3: Cleanup with error + fakeClient.deleteError = xerrors.New("delete failed") + runner.workspaceID = uuid.UUID{5, 6, 7, 8} + err = runner.Cleanup(ctx, "test-runner", logWriter) + require.Error(t, err) + require.Contains(t, err.Error(), "delete external workspace") +} + +// fakeClientWithCleanupTracking extends fakeClient to track deleteWorkspace calls +type fakeClientWithCleanupTracking struct { + *fakeClient + deleteWorkspaceCalls []uuid.UUID + deleteError error +} + +func (c *fakeClientWithCleanupTracking) deleteWorkspace(ctx context.Context, workspaceID uuid.UUID) error { + c.deleteWorkspaceCalls = append(c.deleteWorkspaceCalls, workspaceID) + c.logger.Debug(ctx, "called fake DeleteWorkspace with tracking", slog.F("workspace_id", workspaceID.String())) + return c.deleteError +} From 8ee6e9457e3c3da2d65293fe3c71197e85e6485b Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Wed, 19 Nov 2025 10:35:31 +0400 Subject: [PATCH 170/255] fix: wait for build in task status load generator (#20800) Wait for the External workspace build job to complete before attempting to pull its credentials from Coder. This resolves a race in the load generator. --- scaletest/taskstatus/client.go | 61 +++---- scaletest/taskstatus/run.go | 95 ++++++++++- scaletest/taskstatus/run_internal_test.go | 188 ++++++++++++++++++++-- 3 files changed, 283 insertions(+), 61 deletions(-) diff --git a/scaletest/taskstatus/client.go b/scaletest/taskstatus/client.go index badc6856f5140..d60f20ab8be07 100644 --- a/scaletest/taskstatus/client.go +++ b/scaletest/taskstatus/client.go @@ -11,14 +11,9 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/quartz" ) -// createExternalWorkspaceResult contains the results from creating an external workspace. -type createExternalWorkspaceResult struct { - WorkspaceID uuid.UUID - AgentToken string -} - // client abstracts the details of using codersdk.Client for workspace operations. // This interface allows for easier testing by enabling mock implementations and // provides a cleaner separation of concerns. @@ -27,9 +22,14 @@ type createExternalWorkspaceResult struct { // 1. Create the client with newClient(coderClient) // 2. Configure logging when the io.Writer is available in Run() type client interface { - // createExternalWorkspace creates an external workspace and returns the workspace ID - // and agent token for the first external agent found in the workspace resources. - createExternalWorkspace(ctx context.Context, req codersdk.CreateWorkspaceRequest) (createExternalWorkspaceResult, error) + // CreateUserWorkspace creates a workspace for a user. + CreateUserWorkspace(ctx context.Context, userID string, req codersdk.CreateWorkspaceRequest) (codersdk.Workspace, error) + + // WorkspaceByOwnerAndName retrieves a workspace by owner and name. + WorkspaceByOwnerAndName(ctx context.Context, owner string, name string, params codersdk.WorkspaceOptions) (codersdk.Workspace, error) + + // WorkspaceExternalAgentCredentials retrieves credentials for an external agent. + WorkspaceExternalAgentCredentials(ctx context.Context, workspaceID uuid.UUID, agentName string) (codersdk.ExternalAgentCredentials, error) // watchWorkspace watches for updates to a workspace. watchWorkspace(ctx context.Context, workspaceID uuid.UUID) (<-chan codersdk.Workspace, error) @@ -56,48 +56,28 @@ type appStatusPatcher interface { // codersdk.Client. type sdkClient struct { coderClient *codersdk.Client + clock quartz.Clock + logger slog.Logger } // newClient creates a new client implementation using the provided codersdk.Client. func newClient(coderClient *codersdk.Client) client { return &sdkClient{ coderClient: coderClient, + clock: quartz.NewReal(), } } -func (c *sdkClient) createExternalWorkspace(ctx context.Context, req codersdk.CreateWorkspaceRequest) (createExternalWorkspaceResult, error) { - // Create the workspace - workspace, err := c.coderClient.CreateUserWorkspace(ctx, codersdk.Me, req) - if err != nil { - return createExternalWorkspaceResult{}, err - } - - // Get the workspace with latest build details - workspace, err = c.coderClient.WorkspaceByOwnerAndName(ctx, codersdk.Me, workspace.Name, codersdk.WorkspaceOptions{}) - if err != nil { - return createExternalWorkspaceResult{}, err - } +func (c *sdkClient) CreateUserWorkspace(ctx context.Context, userID string, req codersdk.CreateWorkspaceRequest) (codersdk.Workspace, error) { + return c.coderClient.CreateUserWorkspace(ctx, userID, req) +} - // Find external agents in resources - for _, resource := range workspace.LatestBuild.Resources { - if resource.Type != "coder_external_agent" || len(resource.Agents) == 0 { - continue - } - - // Get credentials for the first agent - agent := resource.Agents[0] - credentials, err := c.coderClient.WorkspaceExternalAgentCredentials(ctx, workspace.ID, agent.Name) - if err != nil { - return createExternalWorkspaceResult{}, err - } - - return createExternalWorkspaceResult{ - WorkspaceID: workspace.ID, - AgentToken: credentials.AgentToken, - }, nil - } +func (c *sdkClient) WorkspaceByOwnerAndName(ctx context.Context, owner string, name string, params codersdk.WorkspaceOptions) (codersdk.Workspace, error) { + return c.coderClient.WorkspaceByOwnerAndName(ctx, owner, name, params) +} - return createExternalWorkspaceResult{}, xerrors.Errorf("no external agent found in workspace") +func (c *sdkClient) WorkspaceExternalAgentCredentials(ctx context.Context, workspaceID uuid.UUID, agentName string) (codersdk.ExternalAgentCredentials, error) { + return c.coderClient.WorkspaceExternalAgentCredentials(ctx, workspaceID, agentName) } func (c *sdkClient) watchWorkspace(ctx context.Context, workspaceID uuid.UUID) (<-chan codersdk.Workspace, error) { @@ -118,6 +98,7 @@ func (c *sdkClient) deleteWorkspace(ctx context.Context, workspaceID uuid.UUID) func (c *sdkClient) initialize(logger slog.Logger) { // Configure the coder client logging + c.logger = logger c.coderClient.SetLogger(logger) c.coderClient.SetLogBodies(true) } diff --git a/scaletest/taskstatus/run.go b/scaletest/taskstatus/run.go index 018ec72afedf0..87f0cbedd3b29 100644 --- a/scaletest/taskstatus/run.go +++ b/scaletest/taskstatus/run.go @@ -23,6 +23,12 @@ import ( const statusUpdatePrefix = "scaletest status update:" +// createExternalWorkspaceResult contains the results from creating an external workspace. +type createExternalWorkspaceResult struct { + workspaceID uuid.UUID + agentToken string +} + type Runner struct { client client patcher appStatusPatcher @@ -65,6 +71,10 @@ func (r *Runner) Run(ctx context.Context, name string, logs io.Writer) error { } }() + // ensure these labels are initialized, so we see the time series right away in prometheus. + r.cfg.Metrics.MissingStatusUpdatesTotal.WithLabelValues(r.cfg.MetricLabelValues...).Add(0) + r.cfg.Metrics.ReportTaskStatusErrorsTotal.WithLabelValues(r.cfg.MetricLabelValues...).Add(0) + logs = loadtestutil.NewSyncWriter(logs) r.logger = slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug).Named(name) r.client.initialize(r.logger) @@ -74,26 +84,23 @@ func (r *Runner) Run(ctx context.Context, name string, logs io.Writer) error { slog.F("template_id", r.cfg.TemplateID), slog.F("workspace_name", r.cfg.WorkspaceName)) - result, err := r.client.createExternalWorkspace(ctx, codersdk.CreateWorkspaceRequest{ + result, err := r.createExternalWorkspace(ctx, codersdk.CreateWorkspaceRequest{ TemplateID: r.cfg.TemplateID, Name: r.cfg.WorkspaceName, }) if err != nil { + r.cfg.Metrics.ReportTaskStatusErrorsTotal.WithLabelValues(r.cfg.MetricLabelValues...).Inc() return xerrors.Errorf("create external workspace: %w", err) } // Set the workspace ID - r.workspaceID = result.WorkspaceID + r.workspaceID = result.workspaceID r.logger.Info(ctx, "created external workspace", slog.F("workspace_id", r.workspaceID)) // Initialize the patcher with the agent token - r.patcher.initialize(r.logger, result.AgentToken) + r.patcher.initialize(r.logger, result.agentToken) r.logger.Info(ctx, "initialized app status patcher with agent token") - // ensure these labels are initialized, so we see the time series right away in prometheus. - r.cfg.Metrics.MissingStatusUpdatesTotal.WithLabelValues(r.cfg.MetricLabelValues...).Add(0) - r.cfg.Metrics.ReportTaskStatusErrorsTotal.WithLabelValues(r.cfg.MetricLabelValues...).Add(0) - workspaceUpdatesCtx, cancelWorkspaceUpdates := context.WithCancel(ctx) defer cancelWorkspaceUpdates() workspaceUpdatesResult := make(chan error, 1) @@ -257,3 +264,77 @@ func parseStatusMessage(message string) (int, bool) { } return msgNo, true } + +// createExternalWorkspace creates an external workspace and returns the workspace ID +// and agent token for the first external agent found in the workspace resources. +func (r *Runner) createExternalWorkspace(ctx context.Context, req codersdk.CreateWorkspaceRequest) (createExternalWorkspaceResult, error) { + // Create the workspace + workspace, err := r.client.CreateUserWorkspace(ctx, codersdk.Me, req) + if err != nil { + return createExternalWorkspaceResult{}, err + } + + r.logger.Info(ctx, "waiting for workspace build to complete", + slog.F("workspace_name", workspace.Name), + slog.F("workspace_id", workspace.ID)) + + // Poll the workspace until the build is complete + var finalWorkspace codersdk.Workspace + buildComplete := xerrors.New("build complete") // sentinel error + waiter := r.clock.TickerFunc(ctx, 30*time.Second, func() error { + // Get the workspace with latest build details + workspace, err := r.client.WorkspaceByOwnerAndName(ctx, codersdk.Me, workspace.Name, codersdk.WorkspaceOptions{}) + if err != nil { + r.logger.Error(ctx, "failed to poll workspace while waiting for build to complete", slog.Error(err)) + return nil + } + + jobStatus := workspace.LatestBuild.Job.Status + r.logger.Debug(ctx, "checking workspace build status", + slog.F("status", jobStatus), + slog.F("build_id", workspace.LatestBuild.ID)) + + switch jobStatus { + case codersdk.ProvisionerJobSucceeded: + // Build succeeded + r.logger.Info(ctx, "workspace build succeeded") + finalWorkspace = workspace + return buildComplete + case codersdk.ProvisionerJobFailed: + return xerrors.Errorf("workspace build failed: %s", workspace.LatestBuild.Job.Error) + case codersdk.ProvisionerJobCanceled: + return xerrors.Errorf("workspace build was canceled") + case codersdk.ProvisionerJobPending, codersdk.ProvisionerJobRunning, codersdk.ProvisionerJobCanceling: + // Still in progress, continue polling + return nil + default: + return xerrors.Errorf("unexpected job status: %s", jobStatus) + } + }, "createExternalWorkspace") + + err = waiter.Wait() + if err != nil && !xerrors.Is(err, buildComplete) { + return createExternalWorkspaceResult{}, xerrors.Errorf("wait for build completion: %w", err) + } + + // Find external agents in resources + for _, resource := range finalWorkspace.LatestBuild.Resources { + if resource.Type != "coder_external_agent" || len(resource.Agents) == 0 { + continue + } + + // Get credentials for the first agent + agent := resource.Agents[0] + credentials, err := r.client.WorkspaceExternalAgentCredentials(ctx, finalWorkspace.ID, agent.Name) + if err != nil { + return createExternalWorkspaceResult{}, err + } + + return createExternalWorkspaceResult{ + workspaceID: finalWorkspace.ID, + agentToken: credentials.AgentToken, + }, nil + } + + return createExternalWorkspaceResult{}, xerrors.Errorf("no external agent found in workspace") +} diff --git a/scaletest/taskstatus/run_internal_test.go b/scaletest/taskstatus/run_internal_test.go index fe5cdda04746b..7a82d4c6b2ad3 100644 --- a/scaletest/taskstatus/run_internal_test.go +++ b/scaletest/taskstatus/run_internal_test.go @@ -28,13 +28,17 @@ type fakeClient struct { logger slog.Logger // Channels for controlling the behavior - workspaceUpdatesCh chan codersdk.Workspace + workspaceUpdatesCh chan codersdk.Workspace + workspaceByOwnerAndNameStatus chan codersdk.ProvisionerJobStatus + workspaceByOwnerAndNameErrors chan error } func newFakeClient(t *testing.T) *fakeClient { return &fakeClient{ - t: t, - workspaceUpdatesCh: make(chan codersdk.Workspace), + t: t, + workspaceUpdatesCh: make(chan codersdk.Workspace), + workspaceByOwnerAndNameStatus: make(chan codersdk.ProvisionerJobStatus), + workspaceByOwnerAndNameErrors: make(chan error, 1), } } @@ -47,14 +51,62 @@ func (m *fakeClient) watchWorkspace(ctx context.Context, workspaceID uuid.UUID) return m.workspaceUpdatesCh, nil } -const testAgentToken = "test-agent-token" +const ( + testAgentToken = "test-agent-token" + testAgentName = "test-agent" + testWorkspaceName = "test-workspace" +) + +var ( + testWorkspaceID = uuid.UUID{1, 2, 3, 4} + testBuildID = uuid.UUID{5, 6, 7, 8} +) -func (m *fakeClient) createExternalWorkspace(ctx context.Context, req codersdk.CreateWorkspaceRequest) (createExternalWorkspaceResult, error) { - m.logger.Debug(ctx, "called fake CreateExternalWorkspace", slog.F("req", req)) - // Return a fake workspace ID and token for testing - return createExternalWorkspaceResult{ - WorkspaceID: uuid.UUID{1, 2, 3, 4}, // Fake workspace ID - AgentToken: testAgentToken, +func workspaceWithJobStatus(status codersdk.ProvisionerJobStatus) codersdk.Workspace { + return codersdk.Workspace{ + ID: testWorkspaceID, // Fake workspace ID + Name: testWorkspaceName, + LatestBuild: codersdk.WorkspaceBuild{ + ID: testBuildID, + Job: codersdk.ProvisionerJob{ + Status: status, + }, + Resources: []codersdk.WorkspaceResource{ + { + Type: "coder_external_agent", + Agents: []codersdk.WorkspaceAgent{ + { + Name: testAgentName, + }, + }, + }, + }, + }, + } +} + +func (m *fakeClient) CreateUserWorkspace(ctx context.Context, userID string, req codersdk.CreateWorkspaceRequest) (codersdk.Workspace, error) { + m.logger.Debug(ctx, "called fake CreateUserWorkspace", slog.F("user_id", userID), slog.F("req", req)) + return workspaceWithJobStatus(codersdk.ProvisionerJobPending), nil +} + +func (m *fakeClient) WorkspaceByOwnerAndName(ctx context.Context, owner string, name string, params codersdk.WorkspaceOptions) (codersdk.Workspace, error) { + m.logger.Debug(ctx, "called fake WorkspaceByOwnerAndName", slog.F("owner", owner), slog.F("name", name)) + status := <-m.workspaceByOwnerAndNameStatus + var err error + select { + case err = <-m.workspaceByOwnerAndNameErrors: + return codersdk.Workspace{}, err + default: + return workspaceWithJobStatus(status), nil + } +} + +func (m *fakeClient) WorkspaceExternalAgentCredentials(ctx context.Context, workspaceID uuid.UUID, agentName string) (codersdk.ExternalAgentCredentials, error) { + m.logger.Debug(ctx, "called fake WorkspaceExternalAgentCredentials", slog.F("workspace_id", workspaceID), slog.F("agent_name", agentName)) + // Return fake credentials for testing + return codersdk.ExternalAgentCredentials{ + AgentToken: testAgentToken, }, nil } @@ -145,10 +197,12 @@ func TestRunner_Run(t *testing.T) { reportTimes: make(map[int]time.Time), } - tickerTrap := mClock.Trap().TickerFunc("reportTaskStatus") - defer tickerTrap.Close() + reportTickerTrap := mClock.Trap().TickerFunc("reportTaskStatus") + defer reportTickerTrap.Close() sinceTrap := mClock.Trap().Since("watchWorkspaceUpdates") defer sinceTrap.Close() + buildTickerTrap := mClock.Trap().TickerFunc("createExternalWorkspace") + defer buildTickerTrap.Close() // Run the runner in a goroutine runErr := make(chan error, 1) @@ -156,6 +210,12 @@ func TestRunner_Run(t *testing.T) { runErr <- runner.Run(ctx, "test-runner", testutil.NewTestLogWriter(t)) }() + // complete the build + buildTickerTrap.MustWait(ctx).MustRelease(ctx) + w := mClock.Advance(30 * time.Second) + testutil.RequireSend(ctx, t, fClient.workspaceByOwnerAndNameStatus, codersdk.ProvisionerJobSucceeded) + w.MustWait(ctx) + // Wait for the runner to connect and watch workspace connectedWaitGroup.Wait() @@ -163,7 +223,7 @@ func TestRunner_Run(t *testing.T) { close(startReporting) // Wait for the initial TickerFunc call before advancing time, otherwise our ticks will be off. - tickerTrap.MustWait(ctx).MustRelease(ctx) + reportTickerTrap.MustWait(ctx).MustRelease(ctx) // at this point, the patcher must be initialized require.Equal(t, testAgentToken, fPatcher.agentToken) @@ -263,6 +323,8 @@ func TestRunner_RunMissedUpdate(t *testing.T) { defer tickerTrap.Close() sinceTrap := mClock.Trap().Since("watchWorkspaceUpdates") defer sinceTrap.Close() + buildTickerTrap := mClock.Trap().TickerFunc("createExternalWorkspace") + defer buildTickerTrap.Close() // Run the runner in a goroutine runErr := make(chan error, 1) @@ -270,6 +332,12 @@ func TestRunner_RunMissedUpdate(t *testing.T) { runErr <- runner.Run(runCtx, "test-runner", testutil.NewTestLogWriter(t)) }() + // complete the build + buildTickerTrap.MustWait(testCtx).MustRelease(testCtx) + w := mClock.Advance(30 * time.Second) + testutil.RequireSend(testCtx, t, fClient.workspaceByOwnerAndNameStatus, codersdk.ProvisionerJobSucceeded) + w.MustWait(testCtx) + // Wait for the runner to connect and watch workspace connectedWaitGroup.Wait() @@ -378,13 +446,20 @@ func TestRunner_Run_WithErrors(t *testing.T) { tickerTrap := mClock.Trap().TickerFunc("reportTaskStatus") defer tickerTrap.Close() - + buildTickerTrap := mClock.Trap().TickerFunc("createExternalWorkspace") + defer buildTickerTrap.Close() // Run the runner in a goroutine runErr := make(chan error, 1) go func() { runErr <- runner.Run(runCtx, "test-runner", testutil.NewTestLogWriter(t)) }() + // complete the build + buildTickerTrap.MustWait(testCtx).MustRelease(testCtx) + w := mClock.Advance(30 * time.Second) + testutil.RequireSend(testCtx, t, fClient.workspaceByOwnerAndNameStatus, codersdk.ProvisionerJobSucceeded) + w.MustWait(testCtx) + connectedWaitGroup.Wait() close(startReporting) @@ -430,6 +505,91 @@ func TestRunner_Run_WithErrors(t *testing.T) { assert.True(t, reportTaskStatusErrorsFound, "report task status errors metric not found") } +func TestRunner_Run_BuildFailed(t *testing.T) { + t.Parallel() + + testCtx := testutil.Context(t, testutil.WaitShort) + runCtx, cancel := context.WithCancel(testCtx) + defer cancel() + + mClock := quartz.NewMock(t) + fClient := newFakeClient(t) + fPatcher := newFakeAppStatusPatcher(t) + templateID := uuid.UUID{5, 6, 7, 8} + workspaceName := "test-workspace" + appSlug := "test-app" + + reg := prometheus.NewRegistry() + metrics := NewMetrics(reg, "test") + + connectedWaitGroup := &sync.WaitGroup{} + connectedWaitGroup.Add(1) + startReporting := make(chan struct{}) + + cfg := Config{ + TemplateID: templateID, + WorkspaceName: workspaceName, + AppSlug: appSlug, + ConnectedWaitGroup: connectedWaitGroup, + StartReporting: startReporting, + ReportStatusPeriod: 10 * time.Second, + ReportStatusDuration: 35 * time.Second, + Metrics: metrics, + MetricLabelValues: []string{"test"}, + } + runner := &Runner{ + client: fClient, + patcher: fPatcher, + cfg: cfg, + clock: mClock, + reportTimes: make(map[int]time.Time), + } + + buildTickerTrap := mClock.Trap().TickerFunc("createExternalWorkspace") + defer buildTickerTrap.Close() + // Run the runner in a goroutine + runErr := make(chan error, 1) + go func() { + runErr <- runner.Run(runCtx, "test-runner", testutil.NewTestLogWriter(t)) + }() + + // complete the build + buildTickerTrap.MustWait(testCtx).MustRelease(testCtx) + w := mClock.Advance(30 * time.Second) + testutil.RequireSend(testCtx, t, fClient.workspaceByOwnerAndNameStatus, codersdk.ProvisionerJobFailed) + w.MustWait(testCtx) + + connectedWaitGroup.Wait() + + // Wait for the runner to complete + err := testutil.RequireReceive(testCtx, t, runErr) + require.ErrorContains(t, err, "workspace build failed") + + // Verify metrics were updated correctly + metricFamilies, err := reg.Gather() + require.NoError(t, err) + + var missingUpdatesFound bool + var reportTaskStatusErrorsFound bool + for _, mf := range metricFamilies { + switch mf.GetName() { + case "coderd_scaletest_missing_status_updates_total": + missingUpdatesFound = true + require.Len(t, mf.GetMetric(), 1) + counter := mf.GetMetric()[0].GetCounter() + assert.Equal(t, float64(0), counter.GetValue()) + case "coderd_scaletest_report_task_status_errors_total": + reportTaskStatusErrorsFound = true + require.Len(t, mf.GetMetric(), 1) + counter := mf.GetMetric()[0].GetCounter() + assert.Equal(t, float64(1), counter.GetValue()) + } + } + + assert.True(t, missingUpdatesFound, "missing updates metric not found") + assert.True(t, reportTaskStatusErrorsFound, "report task status errors metric not found") +} + func TestParseStatusMessage(t *testing.T) { t.Parallel() From 8e22cd707aa5f3aa58fc511a57bd2b6ae8144571 Mon Sep 17 00:00:00 2001 From: Danielle Maywood Date: Wed, 19 Nov 2025 11:44:22 +0000 Subject: [PATCH 171/255] feat(site): add startup script error alerts to Task Page (#20820) Refactors Task page UI to show startup script errors as compact warning buttons in the topbar. Closes https://github.com/coder/coder/issues/20418 --- site/src/pages/TaskPage/TaskPage.stories.tsx | 112 ++++++++++++++++++ .../TaskStartupWarningButton.stories.tsx | 37 ++++++ .../TaskPage/TaskStartupWarningButton.tsx | 109 +++++++++++++++++ site/src/pages/TaskPage/TaskTopbar.tsx | 5 + 4 files changed, 263 insertions(+) create mode 100644 site/src/pages/TaskPage/TaskStartupWarningButton.stories.tsx create mode 100644 site/src/pages/TaskPage/TaskStartupWarningButton.tsx diff --git a/site/src/pages/TaskPage/TaskPage.stories.tsx b/site/src/pages/TaskPage/TaskPage.stories.tsx index b247a2a16b377..27f0b60e3a16f 100644 --- a/site/src/pages/TaskPage/TaskPage.stories.tsx +++ b/site/src/pages/TaskPage/TaskPage.stories.tsx @@ -7,6 +7,7 @@ import { MockTasks, MockUserOwner, MockWorkspace, + MockWorkspaceAgent, MockWorkspaceAgentLogSource, MockWorkspaceAgentReady, MockWorkspaceAgentStarting, @@ -218,6 +219,117 @@ export const WaitingStartupScripts: Story = { }, }; +export const StartupScriptError: Story = { + decorators: [withWebSocket], + parameters: { + queries: [ + { + key: ["tasks", MockTask.owner_name, MockTask.id], + data: { + ...MockTask, + workspace_agent_lifecycle: "start_error", + }, + }, + { + key: [ + "workspace", + MockTask.owner_name, + MockTask.workspace_name, + "settings", + ], + data: { + ...MockWorkspace, + latest_build: { + ...MockWorkspace.latest_build, + has_ai_task: true, + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgent], + }, + ], + }, + }, + }, + ], + webSocket: [ + { + event: "message", + data: JSON.stringify( + [ + "Cloning Git repository...", + "Starting application...", + "\x1b[91mError: Failed to connect to database", + "\x1b[91mStartup script exited with code 1", + ].map((line, index) => ({ + id: index, + level: index >= 2 ? "error" : "info", + output: line, + source_id: MockWorkspaceAgentLogSource.id, + created_at: new Date("2024-01-01T12:00:00Z").toISOString(), + })), + ), + }, + ], + }, +}; + +export const StartupScriptTimeout: Story = { + decorators: [withWebSocket], + parameters: { + queries: [ + { + key: ["tasks", MockTask.owner_name, MockTask.id], + data: { + ...MockTask, + workspace_agent_lifecycle: "start_timeout", + }, + }, + { + key: [ + "workspace", + MockTask.owner_name, + MockTask.workspace_name, + "settings", + ], + data: { + ...MockWorkspace, + latest_build: { + ...MockWorkspace.latest_build, + has_ai_task: true, + resources: [ + { + ...MockWorkspaceResource, + agents: [MockWorkspaceAgent], + }, + ], + }, + }, + }, + ], + webSocket: [ + { + event: "message", + data: JSON.stringify( + [ + "Cloning Git repository...", + "Starting application...", + "Waiting for dependencies...", + "Still waiting...", + "\x1b[93mWarning: Startup script exceeded timeout limit", + ].map((line, index) => ({ + id: index, + level: index === 4 ? "warn" : "info", + output: line, + source_id: MockWorkspaceAgentLogSource.id, + created_at: new Date("2024-01-01T12:00:00Z").toISOString(), + })), + ), + }, + ], + }, +}; + export const SidebarAppNotFound: Story = { beforeEach: () => { const [task, workspace] = mockTaskWithWorkspace( diff --git a/site/src/pages/TaskPage/TaskStartupWarningButton.stories.tsx b/site/src/pages/TaskPage/TaskStartupWarningButton.stories.tsx new file mode 100644 index 0000000000000..482fa396016d7 --- /dev/null +++ b/site/src/pages/TaskPage/TaskStartupWarningButton.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { TaskStartupWarningButton } from "./TaskStartupWarningButton"; + +const meta: Meta = { + title: "pages/TaskPage/TaskStartupWarningButton", + component: TaskStartupWarningButton, + parameters: { + layout: "padded", + }, +}; + +export default meta; +type Story = StoryObj; + +export const StartError: Story = { + args: { + lifecycleState: "start_error", + }, +}; + +export const StartTimeout: Story = { + args: { + lifecycleState: "start_timeout", + }, +}; + +export const NoWarning: Story = { + args: { + lifecycleState: "ready", + }, +}; + +export const NullLifecycle: Story = { + args: { + lifecycleState: null, + }, +}; diff --git a/site/src/pages/TaskPage/TaskStartupWarningButton.tsx b/site/src/pages/TaskPage/TaskStartupWarningButton.tsx new file mode 100644 index 0000000000000..fab1b1a9b32e6 --- /dev/null +++ b/site/src/pages/TaskPage/TaskStartupWarningButton.tsx @@ -0,0 +1,109 @@ +import type { WorkspaceAgentLifecycle } from "api/typesGenerated"; +import { Button } from "components/Button/Button"; +import { Link } from "components/Link/Link"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; +import { TriangleAlertIcon } from "lucide-react"; +import type { FC } from "react"; +import { docs } from "utils/docs"; + +type TaskStartupWarningButtonProps = { + lifecycleState?: WorkspaceAgentLifecycle | null; +}; + +export const TaskStartupWarningButton: FC = ({ + lifecycleState, +}) => { + switch (lifecycleState) { + case "start_error": + return ; + case "start_timeout": + return ; + default: + return null; + } +}; + +type StartupWarningButtonBaseProps = { + label: string; + errorMessage: string; +}; + +const StartupWarningButtonBase: FC = ({ + label, + errorMessage, +}) => { + return ( + + + + + + +

+ A workspace{" "} + + {errorMessage} + + . We recommend{" "} + + debugging the startup script + {" "} + because{" "} + + your workspace may be incomplete + + . +

+
+
+
+ ); +}; + +const ErrorScriptButton: FC = () => { + return ( + + ); +}; + +const TimeoutScriptButton: FC = () => { + return ( + + ); +}; diff --git a/site/src/pages/TaskPage/TaskTopbar.tsx b/site/src/pages/TaskPage/TaskTopbar.tsx index 3d22631ae14b8..b5affcbfffe0b 100644 --- a/site/src/pages/TaskPage/TaskTopbar.tsx +++ b/site/src/pages/TaskPage/TaskTopbar.tsx @@ -16,6 +16,7 @@ import { } from "lucide-react"; import type { FC } from "react"; import { Link as RouterLink } from "react-router"; +import { TaskStartupWarningButton } from "./TaskStartupWarningButton"; import { TaskStatusLink } from "./TaskStatusLink"; type TaskTopbarProps = { task: Task; workspace: Workspace }; @@ -46,6 +47,10 @@ export const TaskTopbar: FC = ({ task, workspace }) => { )}
+ + From f6556fce9f2917f7c0db399b3dd54afd8e340bae Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 19 Nov 2025 15:10:59 +0200 Subject: [PATCH 172/255] test(coderd/workspaceapps/apptest): fix lastusedat assertion for all test (#20827) The test flake can be verified by setting `ReportInterval` to a really low value, like `100 * time.Millisecond`. We now set it to a really high value to avoid triggering flush without manually calling the function in test. This can easily happen because the default value is 30s and we run tests in parallel. The assertion typically happens such that: [use workspace] -> [fetch previous last used] -> [flush] -> [fetch new last used] When this edge case is triggered: [use workspace] -> [report interval flush] -> [fetch previous last used] -> [flush] -> [fetch new last used] In this case, both the previous and new last used will be the same, breaking the test assertion. Fixes coder/internal#960 Fixes coder/internal#975 --- coderd/workspaceapps/apptest/setup.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go index 7fef20503bc2b..65eebf8ecada5 100644 --- a/coderd/workspaceapps/apptest/setup.go +++ b/coderd/workspaceapps/apptest/setup.go @@ -195,6 +195,22 @@ func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *De if opts.DisableSubdomainApps { opts.AppHost = "" } + if opts.StatsCollectorOptions.ReportInterval == 0 { + // Set to a really high value to avoid triggering flush without manually + // calling the function in test. This can easily happen because the + // default value is 30s and we run tests in parallel. The assertion + // typically happens such that: + // + // [use workspace] -> [fetch previous last used] -> [flush] -> [fetch new last used] + // + // When this edge case is triggered: + // + // [use workspace] -> [report interval flush] -> [fetch previous last used] -> [flush] -> [fetch new last used] + // + // In this case, both the previous and new last used will be the same, + // breaking the test assertion. + opts.StatsCollectorOptions.ReportInterval = 9001 * time.Hour + } deployment := factory(t, opts) From a10c5ff3818f4a51afb5e5f5f0263a18ad1290f0 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 19 Nov 2025 09:34:19 -0600 Subject: [PATCH 173/255] chore: protect build timings insert for invalid enums (#20821) Database insert errors will fail the transaction. So this error is fatal. Properly return it for a better error call stack, and not just hiding the error in the logs. --- coderd/provisionerdserver/provisionerdserver.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 56670d8a2d9e8..39e707a87f016 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -2175,6 +2175,12 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro continue } + // Scan does not guarantee validity + if !stg.Valid() { + s.Logger.Warn(ctx, "invalid stage, will fail insert based one enum", slog.F("value", t.Stage)) + continue + } + params.Stage = append(params.Stage, stg) params.Source = append(params.Source, t.Source) params.Resource = append(params.Resource, t.Resource) @@ -2184,8 +2190,11 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro } _, err = db.InsertProvisionerJobTimings(ctx, params) if err != nil { - // Log error but don't fail the whole transaction for non-critical data + // A database error here will "fail" this transaction. Making this error fatal. + // If this error is seen, add checks above to validate the insert parameters. In + // production, timings should not be a fatal error. s.Logger.Warn(ctx, "failed to update provisioner job timings", slog.F("job_id", jobID), slog.Error(err)) + return xerrors.Errorf("update provisioner job timings: %w", err) } // On start, we want to ensure that workspace agents timeout statuses From aff208048e56fa63893917ef026c8845acea3fc4 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 19 Nov 2025 09:35:33 -0600 Subject: [PATCH 174/255] feat: fix build timeline to include entire stage timings (#20805) Measure entire stage durations for each terraform cmd execution --- provisioner/terraform/executor.go | 10 +++- provisioner/terraform/inittimings.go | 27 --------- provisioner/terraform/provision.go | 9 +-- provisioner/terraform/timings.go | 59 +++++++++++++------ .../terraform/timings_internal_test.go | 15 +++++ provisioner/terraform/timings_test.go | 8 +++ .../WorkspaceTiming/ResourcesChart.tsx | 33 ++++++++--- .../WorkspaceTiming/WorkspaceTimings.tsx | 13 +++- .../WorkspaceTiming/storybookData.ts | 24 +++++++- 9 files changed, 133 insertions(+), 65 deletions(-) diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index 2a4fbb3d1808d..3d9270a6ddbab 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -325,7 +325,9 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l <-doneErr }() + endStage := e.timings.startStage(database.ProvisionerJobTimingStagePlan) err := e.execWriteOutput(ctx, killCtx, args, env, outWriter, errWriter) + endStage(err) if err != nil { return nil, xerrors.Errorf("terraform plan: %w", err) } @@ -596,10 +598,15 @@ func (e *executor) apply( <-doneErr }() + // `terraform apply` + endStage := e.timings.startStage(database.ProvisionerJobTimingStageApply) err := e.execWriteOutput(ctx, killCtx, args, env, outWriter, errWriter) + endStage(err) if err != nil { return nil, xerrors.Errorf("terraform apply: %w", err) } + + // `terraform show` & `terraform graph` state, err := e.stateResources(ctx, killCtx) if err != nil { return nil, err @@ -610,12 +617,13 @@ func (e *executor) apply( return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err) } + agg := e.timings.aggregate() return &proto.ApplyComplete{ Parameters: state.Parameters, Resources: state.Resources, ExternalAuthProviders: state.ExternalAuthProviders, State: stateContent, - Timings: e.timings.aggregate(), + Timings: agg, AiTasks: state.AITasks, }, nil } diff --git a/provisioner/terraform/inittimings.go b/provisioner/terraform/inittimings.go index e72d237b5268f..7905ead772e82 100644 --- a/provisioner/terraform/inittimings.go +++ b/provisioner/terraform/inittimings.go @@ -137,30 +137,3 @@ func (t *timingAggregator) finishPrevious(ts time.Time, s *timingSpan) { t.lookupMu.Unlock() } - -// mergeInitTimings merges manual init timings with existing timings that are -// sourced by the logs. This is done because prior to Terraform v1.9, init logs -// did not have a `-json` formatting option. -// So before v1.9, the init stage is manually timed outside the `terraform init`. -// After v1.9, the init stage is timed via logs. -func mergeInitTimings(manualInit []*proto.Timing, existing []*proto.Timing) []*proto.Timing { - initFailed := slices.ContainsFunc(existing, func(timing *proto.Timing) bool { - return timing.State == proto.TimingState_FAILED - }) - - if initFailed { - // The init logs do not provide enough information for failed init timings. - // So use the manual timings in this case. - return append(manualInit, existing...) - } - - hasInitStage := slices.ContainsFunc(existing, func(timing *proto.Timing) bool { - return timing.Stage == string(database.ProvisionerJobTimingStageInit) - }) - - if hasInitStage { - return existing - } - - return append(manualInit, existing...) -} diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index 2445a396f6145..c99ee55ad8cc6 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -110,12 +110,11 @@ func (s *server) Plan( // The JSON output of `terraform init` doesn't include discrete fields for capturing timings of each plugin, // so we capture the whole init process. initTimings := newTimingAggregator(database.ProvisionerJobTimingStageInit) - initTimings.ingest(createInitTimingsEvent(timingInitStart)) + endStage := initTimings.startStage(database.ProvisionerJobTimingStageInit) err = e.init(ctx, killCtx, sess) + endStage(err) if err != nil { - initTimings.ingest(createInitTimingsEvent(timingInitErrored)) - s.logger.Debug(ctx, "init failed", slog.Error(err)) // Special handling for "text file busy" c.f. https://github.com/coder/coder/issues/14726 @@ -148,8 +147,6 @@ func (s *server) Plan( s.logger.Error(ctx, "failed to get modules from disk", slog.Error(err)) } - initTimings.ingest(createInitTimingsEvent(timingInitComplete)) - s.logger.Debug(ctx, "ran initialization") env, err := provisionEnv(sess.Config, request.Metadata, request.PreviousParameterValues, request.RichParameterValues, request.ExternalAuthProviders) @@ -170,7 +167,7 @@ func (s *server) Plan( // Prepend init timings since they occur prior to plan timings. // Order is irrelevant; this is merely indicative. - resp.Timings = mergeInitTimings(initTimings.aggregate(), resp.Timings) + resp.Timings = append(initTimings.aggregate(), resp.Timings...) // mergeInitTimings(initTimings.aggregate(), resp.Timings) resp.Modules = modules return resp } diff --git a/provisioner/terraform/timings.go b/provisioner/terraform/timings.go index d2fe74239826e..0b150d2eafd4d 100644 --- a/provisioner/terraform/timings.go +++ b/provisioner/terraform/timings.go @@ -19,6 +19,13 @@ type timingKind string // Copied from https://github.com/hashicorp/terraform/blob/01c0480e77263933b2b086dc8d600a69f80fad2d/internal/command/jsonformat/renderer.go // We cannot reference these because they're in an internal package. const ( + // Stage markers are used to denote the beginning and end of stages. Without + // these, only discrete events (i.e. resource changes) within stages can be + // measured, which may omit setup/teardown time or other unmeasured overhead. + timingStageStart timingKind = "stage_start" + timingStageEnd timingKind = "stage_end" + timingStageError timingKind = "stage_error" + timingApplyStart timingKind = "apply_start" timingApplyProgress timingKind = "apply_progress" timingApplyComplete timingKind = "apply_complete" @@ -37,9 +44,6 @@ const ( timingResourceDrift timingKind = "resource_drift" timingVersion timingKind = "version" // These are not part of message_types, but we want to track init/graph timings as well. - timingInitStart timingKind = "init_start" - timingInitComplete timingKind = "init_complete" - timingInitErrored timingKind = "init_errored" timingGraphStart timingKind = "graph_start" timingGraphComplete timingKind = "graph_complete" timingGraphErrored timingKind = "graph_errored" @@ -109,13 +113,13 @@ func (t *timingAggregator) ingest(ts time.Time, s *timingSpan) { ts = dbtime.Time(ts.UTC()) switch s.kind { - case timingApplyStart, timingProvisionStart, timingRefreshStart, timingInitStart, timingGraphStart: + case timingApplyStart, timingProvisionStart, timingRefreshStart, timingGraphStart, timingStageStart: s.start = ts s.state = proto.TimingState_STARTED - case timingApplyComplete, timingProvisionComplete, timingRefreshComplete, timingInitComplete, timingGraphComplete: + case timingApplyComplete, timingProvisionComplete, timingRefreshComplete, timingGraphComplete, timingStageEnd: s.end = ts s.state = proto.TimingState_COMPLETED - case timingApplyErrored, timingProvisionErrored, timingInitErrored, timingGraphErrored: + case timingApplyErrored, timingProvisionErrored, timingGraphErrored, timingStageError: s.end = ts s.state = proto.TimingState_FAILED case timingInitOutput: @@ -176,8 +180,35 @@ func (t *timingAggregator) aggregate() []*proto.Timing { return out } +// startStage denotes the beginning of a stage and returns a function which +// should be called to mark the end of the stage. This is used to measure a +// stage's total duration across all it's discrete events and unmeasured +// overhead/events. +func (t *timingAggregator) startStage(stage database.ProvisionerJobTimingStage) (end func(err error)) { + ts := timingSpan{ + kind: timingStageStart, + stage: stage, + resource: "coder_stage_" + string(stage), + action: "terraform", + provider: "coder", + } + endTs := ts + t.ingest(dbtime.Now(), &ts) + + return func(err error) { + endTs.kind = timingStageEnd + if err != nil { + endTs.kind = timingStageError + } + t.ingest(dbtime.Now(), &endTs) + } +} + func (l timingKind) Valid() bool { return slices.Contains([]timingKind{ + timingStageStart, + timingStageEnd, + timingStageError, timingApplyStart, timingApplyProgress, timingApplyComplete, @@ -194,9 +225,6 @@ func (l timingKind) Valid() bool { timingOutputs, timingResourceDrift, timingVersion, - timingInitStart, - timingInitComplete, - timingInitErrored, timingGraphStart, timingGraphComplete, timingGraphErrored, @@ -210,7 +238,9 @@ func (l timingKind) Valid() bool { // if all other attributes are identical. func (l timingKind) Category() string { switch l { - case timingInitStart, timingInitComplete, timingInitErrored, timingInitOutput: + case timingStageStart, timingStageEnd, timingStageError: + return "stage" + case timingInitOutput: return "init" case timingGraphStart, timingGraphComplete, timingGraphErrored: return "graph" @@ -252,15 +282,6 @@ func (e *timingSpan) toProto() *proto.Timing { } } -func createInitTimingsEvent(event timingKind) (time.Time, *timingSpan) { - return dbtime.Now(), &timingSpan{ - kind: event, - action: "initializing terraform", - provider: "terraform", - resource: "init", - } -} - func createGraphTimingsEvent(event timingKind) (time.Time, *timingSpan) { return dbtime.Now(), &timingSpan{ kind: event, diff --git a/provisioner/terraform/timings_internal_test.go b/provisioner/terraform/timings_internal_test.go index 552bec5a1953e..99f057a97e6af 100644 --- a/provisioner/terraform/timings_internal_test.go +++ b/provisioner/terraform/timings_internal_test.go @@ -155,3 +155,18 @@ func printTimings(t *testing.T, timings []*proto.Timing) { terraform_internal.PrintTiming(t, a) } } + +func TestTimingStages(t *testing.T) { + t.Parallel() + + agg := &timingAggregator{ + stage: database.ProvisionerJobTimingStageApply, + stateLookup: make(map[uint64]*timingSpan), + } + + end := agg.startStage(database.ProvisionerJobTimingStageApply) + end(nil) + + evts := agg.aggregate() + require.Len(t, evts, 1) +} diff --git a/provisioner/terraform/timings_test.go b/provisioner/terraform/timings_test.go index fe167b830ff05..7a9ac84220a51 100644 --- a/provisioner/terraform/timings_test.go +++ b/provisioner/terraform/timings_test.go @@ -6,11 +6,13 @@ import ( "context" "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/util/slice" terraform_internal "github.com/coder/coder/v2/provisioner/terraform/internal" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/testutil" @@ -95,6 +97,12 @@ func TestTimingsFromProvision(t *testing.T) { // Sort the timings stably to keep reduce flakiness. terraform_internal.StableSortTimings(t, timings) + // `coder_stage_` timings use `dbtime.Now()`, which makes them hard to compare to + // a static set of expected timings. Filter them out. This test is good for + // testing timings sourced from terraform logs, not internal coder timings. + timings = slice.Filter(timings, func(tim *proto.Timing) bool { + return !strings.HasPrefix(tim.Resource, "coder_stage_") + }) // Then: the received timings should match the expected values below. // NOTE: These timings have been encoded to JSON format to make the tests more readable. diff --git a/site/src/modules/workspaces/WorkspaceTiming/ResourcesChart.tsx b/site/src/modules/workspaces/WorkspaceTiming/ResourcesChart.tsx index f2757ee48e5c0..143b5e51d7431 100644 --- a/site/src/modules/workspaces/WorkspaceTiming/ResourcesChart.tsx +++ b/site/src/modules/workspaces/WorkspaceTiming/ResourcesChart.tsx @@ -52,7 +52,10 @@ export const ResourcesChart: FC = ({ const [ticks, scale] = makeTicks(totalTime); const [filter, setFilter] = useState(""); const visibleTimings = timings.filter( - (t) => !isCoderResource(t.name) && t.name.includes(filter), + // Stage boundaries are also included + (t) => + (!isCoderResource(t.name) || isStageBoundary(t.name)) && + t.name.includes(filter), ); const theme = useTheme(); const legendsByAction = getLegendsByAction(theme); @@ -86,11 +89,16 @@ export const ResourcesChart: FC = ({ {stage.name} stage - {visibleTimings.map((t) => ( - - {t.name} - - ))} + {visibleTimings.map((t) => { + const label = isStageBoundary(t.name) + ? "total stage duration" + : t.name; + return ( + + {label} + + ); + })} @@ -98,8 +106,10 @@ export const ResourcesChart: FC = ({ {visibleTimings.map((t) => { + const stageBoundary = isStageBoundary(t.name); const duration = calcDuration(t.range); const legend = legendsByAction[t.action] ?? { label: t.action }; + const label = stageBoundary ? "total stage duration" : t.name; return ( = ({ - {t.name} - view template + {label} + {/* Stage boundaries should not have these links */} + {!stageBoundary && ( + view template + )} } > @@ -132,6 +145,10 @@ export const ResourcesChart: FC = ({ ); }; +export const isStageBoundary = (resource: string) => { + return resource.startsWith("coder_stage_"); +}; + export const isCoderResource = (resource: string) => { return ( resource.startsWith("data.coder") || diff --git a/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx b/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx index b4cffd4d719d3..c4e0e63228314 100644 --- a/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx +++ b/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx @@ -17,7 +17,11 @@ import { mergeTimeRanges, type TimeRange, } from "./Chart/utils"; -import { isCoderResource, ResourcesChart } from "./ResourcesChart"; +import { + isCoderResource, + isStageBoundary, + ResourcesChart, +} from "./ResourcesChart"; import { ScriptsChart } from "./ScriptsChart"; import { agentStages, @@ -140,6 +144,13 @@ export const WorkspaceTimings: FC = ({ // user and would add noise. const visibleResources = stageTimings.filter((t) => { const isProvisionerTiming = "resource" in t; + + // StageBoundaries are being drawn on the total timeline. + // Do not show them as discrete resources inside the stage view. + if (isProvisionerTiming && isStageBoundary(t.resource)) { + return false; + } + return isProvisionerTiming ? !isCoderResource(t.resource) : true; diff --git a/site/src/modules/workspaces/WorkspaceTiming/storybookData.ts b/site/src/modules/workspaces/WorkspaceTiming/storybookData.ts index 589d95e6153da..c45b56ec5a52e 100644 --- a/site/src/modules/workspaces/WorkspaceTiming/storybookData.ts +++ b/site/src/modules/workspaces/WorkspaceTiming/storybookData.ts @@ -7,9 +7,18 @@ export const WorkspaceTimingsResponse: WorkspaceBuildTimings = { started_at: "2024-10-14T11:30:38.582305Z", ended_at: "2024-10-14T11:30:47.707708Z", stage: "init", - source: "terraform", - action: "initializing terraform", - resource: "state file", + source: "coder", + action: "terraform", + resource: "coder_stage_init", + }, + { + job_id: "86fd4143-d95f-4602-b464-1149ede62269", + started_at: "2024-10-14T11:30:48.105148Z", + ended_at: "2024-10-14T11:30:49.911366Z", + stage: "plan", + source: "coder", + action: "terraform", + resource: "coder_stage_plan", }, { job_id: "86fd4143-d95f-4602-b464-1149ede62269", @@ -310,6 +319,15 @@ export const WorkspaceTimingsResponse: WorkspaceBuildTimings = { action: "building terraform dependency graph", resource: "state file", }, + { + job_id: "86fd4143-d95f-4602-b464-1149ede62269", + started_at: "2024-10-14T11:30:50.161398Z", + ended_at: "2024-10-14T11:30:53.993767Z", + stage: "apply", + source: "coder", + action: "terraform", + resource: "coder_stage_apply", + }, { job_id: "86fd4143-d95f-4602-b464-1149ede62269", started_at: "2024-10-14T11:30:50.861398Z", From 35b9df86b300cceccbb4cdf758762cc5e1a688e7 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 19 Nov 2025 16:02:43 +0000 Subject: [PATCH 175/255] chore(docs): document preset description and icon fields (#20705) Closes https://github.com/coder/coder/issues/20599 Generated by Claude Code, reviewed by me. --- .../extending-templates/parameters.md | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/docs/admin/templates/extending-templates/parameters.md b/docs/admin/templates/extending-templates/parameters.md index 43a477632e7db..57d2582bc8f02 100644 --- a/docs/admin/templates/extending-templates/parameters.md +++ b/docs/admin/templates/extending-templates/parameters.md @@ -322,15 +322,33 @@ their needs. ![Template with options in the preset dropdown](../../../images/admin/templates/extend-templates/template-preset-dropdown.png) -Use `coder_workspace_preset` to define the preset parameters. -After you save the template file, the presets will be available for all new -workspace deployments. +Use the +[`coder_workspace_preset`](https://registry.terraform.io/providers/coder/coder/latest/docs/data-sources/workspace_preset) +data source to define the preset parameters. After you save the template file, +the presets will be available for all new workspace deployments. + +### Optional preset fields + +In addition to the required `name` and `parameters` fields, you can enhance your +workspace presets with optional `description` and `icon` fields: + +- **description**: A helpful text description that provides additional context + about the preset. This helps users understand what the preset is for and when + to use it. +- **icon**: A visual icon displayed alongside the preset name in the UI. Use + emoji icons with the format `/emojis/{code}.png` (e.g., + `/emojis/1f1fa-1f1f8.png` for the US flag emoji 🇺🇸). + +For a complete list of all available fields, see the +[Terraform provider documentation](https://registry.terraform.io/providers/coder/coder/latest/docs/data-sources/workspace_preset#schema).
Expand for an example ```tf data "coder_workspace_preset" "goland-gpu" { name = "GoLand with GPU" + description = "Development workspace with GPU acceleration for GoLand IDE" + icon = "/emojis/1f680.png" parameters = { "machine_type" = "n1-standard-1" "attach_gpu" = "true" @@ -339,6 +357,16 @@ data "coder_workspace_preset" "goland-gpu" { } } +data "coder_workspace_preset" "pittsburgh" { + name = "Pittsburgh" + description = "Development workspace hosted in United States" + icon = "/emojis/1f1fa-1f1f8.png" + parameters = { + "region" = "us-pittsburgh" + "machine_type" = "n1-standard-2" + } +} + data "coder_parameter" "machine_type" { name = "machine_type" display_name = "Machine Type" @@ -355,16 +383,23 @@ data "coder_parameter" "attach_gpu" { data "coder_parameter" "gcp_region" { name = "gcp_region" - display_name = "Machine Type" + display_name = "GCP Region" type = "string" - default = "n1-standard-2" + default = "us-central1-a" } data "coder_parameter" "jetbrains_ide" { name = "jetbrains_ide" - display_name = "Machine Type" + display_name = "JetBrains IDE" type = "string" - default = "n1-standard-2" + default = "IU" +} + +data "coder_parameter" "region" { + name = "region" + display_name = "Region" + type = "string" + default = "us-east-1" } ``` From 500c17e25786b3a7481befce1971ac4c38f205f3 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Wed, 19 Nov 2025 19:03:37 +0200 Subject: [PATCH 176/255] feat(agent): add agent unit manager (#20715) relates to: https://github.com/coder/internal/issues/1094 This is number 1 of 5 pull requests in an effort to add agent script ordering. It adds a unit manager, which uses an underlying DAG and a list of subscribers to inform units when their dependencies have changed in status. In follow-up PRs: * This unit manager will be plumbed into the workspace agent struct. * It will then be exposed to users via a new socket based drpc API * The agentsocket API will then become accessible via CLI commands that allow coder scripts to express their dependencies on one another. This is an experimental feature. There may be ways to improve the efficiency of the manager struct, but it is more important to validate this feature with customers before we invest in such optimizations. See the tests for examples of how units may communicate with one another. Actual CLI usage will be analogous. I used an LLM to produce some of these changes, but I have conducted thorough self review and consider this contribution to be ready for an external reviewer. --- agent/unit/graph.go | 2 +- agent/unit/graph_test.go | 8 +- agent/unit/manager.go | 255 ++++++++++++++++ agent/unit/manager_test.go | 594 +++++++++++++++++++++++++++++++++++++ 4 files changed, 853 insertions(+), 6 deletions(-) create mode 100644 agent/unit/manager.go create mode 100644 agent/unit/manager_test.go diff --git a/agent/unit/graph.go b/agent/unit/graph.go index 3d8a6703addf2..e9388680c10d1 100644 --- a/agent/unit/graph.go +++ b/agent/unit/graph.go @@ -58,7 +58,7 @@ func (g *Graph[EdgeType, VertexType]) AddEdge(from, to VertexType, edge EdgeType toID := g.getOrCreateVertexID(to) if g.canReach(to, from) { - return xerrors.Errorf("adding edge (%v -> %v) would create a cycle", from, to) + return xerrors.Errorf("adding edge (%v -> %v): %w", from, to, ErrCycleDetected) } g.gonumGraph.SetEdge(simple.Edge{F: simple.Node(fromID), T: simple.Node(toID)}) diff --git a/agent/unit/graph_test.go b/agent/unit/graph_test.go index 3c76756aee88c..f7d1117be74b3 100644 --- a/agent/unit/graph_test.go +++ b/agent/unit/graph_test.go @@ -148,8 +148,7 @@ func TestGraph(t *testing.T) { graph := &testGraph{} unit1 := &testGraphVertex{Name: "unit1"} err := graph.AddEdge(unit1, unit1, testEdgeCompleted) - require.Error(t, err) - require.ErrorContains(t, err, fmt.Sprintf("adding edge (%v -> %v) would create a cycle", unit1, unit1)) + require.ErrorIs(t, err, unit.ErrCycleDetected) return graph }, @@ -160,8 +159,7 @@ func TestGraph(t *testing.T) { err := graph.AddEdge(unit1, unit2, testEdgeCompleted) require.NoError(t, err) err = graph.AddEdge(unit2, unit1, testEdgeStarted) - require.Error(t, err) - require.ErrorContains(t, err, fmt.Sprintf("adding edge (%v -> %v) would create a cycle", unit2, unit1)) + require.ErrorIs(t, err, unit.ErrCycleDetected) return graph }, @@ -341,7 +339,7 @@ func TestGraphThreadSafety(t *testing.T) { // Verify all attempts correctly returned cycle error for i, err := range cycleErrors { require.Error(t, err, "goroutine %d should have detected cycle", i) - require.Contains(t, err.Error(), "would create a cycle") + require.ErrorIs(t, err, unit.ErrCycleDetected) } // Verify graph remains valid (original chain intact) diff --git a/agent/unit/manager.go b/agent/unit/manager.go new file mode 100644 index 0000000000000..56ec0213c131f --- /dev/null +++ b/agent/unit/manager.go @@ -0,0 +1,255 @@ +package unit + +import ( + "errors" + "sync" + + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/coderd/util/slice" +) + +var ( + ErrUnitNotFound = xerrors.New("unit not found") + ErrUnitAlreadyRegistered = xerrors.New("unit already registered") + ErrCannotUpdateOtherUnit = xerrors.New("cannot update other unit's status") + ErrDependenciesNotSatisfied = xerrors.New("unit dependencies not satisfied") + ErrSameStatusAlreadySet = xerrors.New("same status already set") + ErrCycleDetected = xerrors.New("cycle detected") + ErrFailedToAddDependency = xerrors.New("failed to add dependency") +) + +// Status represents the status of a unit. +type Status string + +// Status constants for dependency tracking. +const ( + StatusNotRegistered Status = "" + StatusPending Status = "pending" + StatusStarted Status = "started" + StatusComplete Status = "completed" +) + +// ID provides a type narrowed representation of the unique identifier of a unit. +type ID string + +// Unit represents a point-in-time snapshot of a vertex in the dependency graph. +// Units may depend on other units, or be depended on by other units. The unit struct +// is not aware of updates made to the dependency graph after it is initialized and should +// not be cached. +type Unit struct { + id ID + status Status + // ready is true if all dependencies are satisfied. + // It does not have an accessor method on Unit, because a unit cannot know whether it is ready. + // Only the Manager can calculate whether a unit is ready based on knowledge of the dependency graph. + // To discourage use of an outdated readiness value, only the Manager should set and return this field. + ready bool +} + +func (u Unit) ID() ID { + return u.id +} + +func (u Unit) Status() Status { + return u.status +} + +// Dependency represents a dependency relationship between units. +type Dependency struct { + Unit ID + DependsOn ID + RequiredStatus Status + CurrentStatus Status + IsSatisfied bool +} + +// Manager provides reactive dependency tracking over a Graph. +// It manages Unit registration, dependency relationships, and status updates +// with automatic recalculation of readiness when dependencies are satisfied. +type Manager struct { + mu sync.RWMutex + + // The underlying graph that stores dependency relationships + graph *Graph[Status, ID] + + // Store vertex instances for each unit to ensure consistent references + units map[ID]Unit +} + +// NewManager creates a new Manager instance. +func NewManager() *Manager { + return &Manager{ + graph: &Graph[Status, ID]{}, + units: make(map[ID]Unit), + } +} + +// Register adds a unit to the manager if it is not already registered. +// If a Unit is already registered (per the ID field), it is not updated. +func (m *Manager) Register(id ID) error { + m.mu.Lock() + defer m.mu.Unlock() + + if m.registered(id) { + return xerrors.Errorf("registering unit %q: %w", id, ErrUnitAlreadyRegistered) + } + + m.units[id] = Unit{ + id: id, + status: StatusPending, + ready: true, + } + + return nil +} + +// registered checks if a unit is registered in the manager. +func (m *Manager) registered(id ID) bool { + return m.units[id].status != StatusNotRegistered +} + +// Unit fetches a unit from the manager. If the unit does not exist, +// it returns the Unit zero-value as a placeholder unit, because +// units may depend on other units that have not yet been created. +func (m *Manager) Unit(id ID) Unit { + m.mu.RLock() + defer m.mu.RUnlock() + + return m.units[id] +} + +func (m *Manager) IsReady(id ID) bool { + m.mu.RLock() + defer m.mu.RUnlock() + + if !m.registered(id) { + return false + } + + return m.units[id].ready +} + +// AddDependency adds a dependency relationship between units. +// The unit depends on the dependsOn unit reaching the requiredStatus. +func (m *Manager) AddDependency(unit ID, dependsOn ID, requiredStatus Status) error { + m.mu.Lock() + defer m.mu.Unlock() + + if !m.registered(unit) { + return xerrors.Errorf("checking registration for unit %q: %w", unit, ErrUnitNotFound) + } + + // Add the dependency edge to the graph + // The edge goes from unit to dependsOn, representing the dependency + err := m.graph.AddEdge(unit, dependsOn, requiredStatus) + if err != nil { + return xerrors.Errorf("adding edge for unit %q: %w", unit, errors.Join(ErrFailedToAddDependency, err)) + } + + // Recalculate readiness for the unit since it now has a new dependency + m.recalculateReadinessUnsafe(unit) + + return nil +} + +// UpdateStatus updates a unit's status and recalculates readiness for affected dependents. +func (m *Manager) UpdateStatus(unit ID, newStatus Status) error { + m.mu.Lock() + defer m.mu.Unlock() + + if !m.registered(unit) { + return xerrors.Errorf("checking registration for unit %q: %w", unit, ErrUnitNotFound) + } + + u := m.units[unit] + if u.status == newStatus { + return xerrors.Errorf("checking status for unit %q: %w", unit, ErrSameStatusAlreadySet) + } + + u.status = newStatus + m.units[unit] = u + + // Get all units that depend on this one (reverse adjacent vertices) + dependents := m.graph.GetReverseAdjacentVertices(unit) + + // Recalculate readiness for all dependents + for _, dependent := range dependents { + m.recalculateReadinessUnsafe(dependent.From) + } + + return nil +} + +// recalculateReadinessUnsafe recalculates the readiness state for a unit. +// This method assumes the caller holds the write lock. +func (m *Manager) recalculateReadinessUnsafe(unit ID) { + u := m.units[unit] + dependencies := m.graph.GetForwardAdjacentVertices(unit) + + allSatisfied := true + for _, dependency := range dependencies { + requiredStatus := dependency.Edge + dependsOnUnit := m.units[dependency.To] + if dependsOnUnit.status != requiredStatus { + allSatisfied = false + break + } + } + + u.ready = allSatisfied + m.units[unit] = u +} + +// GetGraph returns the underlying graph for visualization and debugging. +// This should be used carefully as it exposes the internal graph structure. +func (m *Manager) GetGraph() *Graph[Status, ID] { + return m.graph +} + +// GetAllDependencies returns all dependencies for a unit, both satisfied and unsatisfied. +func (m *Manager) GetAllDependencies(unit ID) ([]Dependency, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + if !m.registered(unit) { + return nil, xerrors.Errorf("checking registration for unit %q: %w", unit, ErrUnitNotFound) + } + + dependencies := m.graph.GetForwardAdjacentVertices(unit) + + var allDependencies []Dependency + + for _, dependency := range dependencies { + dependsOnUnit := m.units[dependency.To] + requiredStatus := dependency.Edge + allDependencies = append(allDependencies, Dependency{ + Unit: unit, + DependsOn: dependsOnUnit.id, + RequiredStatus: requiredStatus, + CurrentStatus: dependsOnUnit.status, + IsSatisfied: dependsOnUnit.status == requiredStatus, + }) + } + + return allDependencies, nil +} + +// GetUnmetDependencies returns a list of unsatisfied dependencies for a unit. +func (m *Manager) GetUnmetDependencies(unit ID) ([]Dependency, error) { + allDependencies, err := m.GetAllDependencies(unit) + if err != nil { + return nil, err + } + + var unmetDependencies []Dependency = slice.Filter(allDependencies, func(dependency Dependency) bool { + return !dependency.IsSatisfied + }) + + return unmetDependencies, nil +} + +// ExportDOT exports the dependency graph to DOT format for visualization. +func (m *Manager) ExportDOT(name string) (string, error) { + return m.graph.ToDOT(name) +} diff --git a/agent/unit/manager_test.go b/agent/unit/manager_test.go new file mode 100644 index 0000000000000..d85b1752a17b7 --- /dev/null +++ b/agent/unit/manager_test.go @@ -0,0 +1,594 @@ +package unit_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/agent/unit" +) + +const ( + unitA unit.ID = "serviceA" + unitB unit.ID = "serviceB" + unitC unit.ID = "serviceC" + unitD unit.ID = "serviceD" +) + +func TestManager_Register(t *testing.T) { + t.Parallel() + + t.Run("RegisterNewUnit", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given: a unit is registered + err := manager.Register(unitA) + require.NoError(t, err) + + // Then: the unit should be ready (no dependencies) + u := manager.Unit(unitA) + assert.Equal(t, unitA, u.ID()) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.True(t, manager.IsReady(unitA)) + }) + + t.Run("RegisterDuplicateUnit", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given: a unit is registered + err := manager.Register(unitA) + require.NoError(t, err) + + // Newly registered units have StatusPending. We update the unit status to StatusStarted, + // so we can later assert that it is not overwritten back to StatusPending by the second + // register call + manager.UpdateStatus(unitA, unit.StatusStarted) + + // When: the unit is registered again + err = manager.Register(unitA) + + // Then: a descriptive error should be returned + require.ErrorIs(t, err, unit.ErrUnitAlreadyRegistered) + + // Then: the unit status should not be overwritten + u := manager.Unit(unitA) + assert.Equal(t, unit.StatusStarted, u.Status()) + assert.True(t, manager.IsReady(unitA)) + }) + + t.Run("RegisterMultipleUnits", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given: multiple units are registered + unitIDs := []unit.ID{unitA, unitB, unitC} + for _, unit := range unitIDs { + err := manager.Register(unit) + require.NoError(t, err) + } + + // Then: all units should be ready initially + for _, unitID := range unitIDs { + u := manager.Unit(unitID) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.True(t, manager.IsReady(unitID)) + } + }) +} + +func TestManager_AddDependency(t *testing.T) { + t.Parallel() + + t.Run("AddDependencyBetweenRegisteredUnits", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given: units A and B are registered + err := manager.Register(unitA) + require.NoError(t, err) + err = manager.Register(unitB) + require.NoError(t, err) + + // Given: Unit A depends on Unit B being unit.StatusStarted + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + + // Then: Unit A should not be ready (depends on B) + u := manager.Unit(unitA) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.False(t, manager.IsReady(unitA)) + + // Then: Unit B should still be ready (no dependencies) + u = manager.Unit(unitB) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.True(t, manager.IsReady(unitB)) + + // When: Unit B is started + err = manager.UpdateStatus(unitB, unit.StatusStarted) + require.NoError(t, err) + + // Then: Unit A should be ready, because its dependency is now in the desired state. + assert.True(t, manager.IsReady(unitA)) + + // When: Unit B is stopped + err = manager.UpdateStatus(unitB, unit.StatusPending) + require.NoError(t, err) + + // Then: Unit A should no longer be ready, because its dependency is not in the desired state. + assert.False(t, manager.IsReady(unitA)) + }) + + t.Run("AddDependencyByAnUnregisteredDependentUnit", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given Unit B is registered + err := manager.Register(unitB) + require.NoError(t, err) + + // Given Unit A depends on Unit B being started + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + + // Then: a descriptive error communicates that the dependency cannot be added + // because the dependent unit must be registered first. + require.ErrorIs(t, err, unit.ErrUnitNotFound) + }) + + t.Run("AddDependencyOnAnUnregisteredUnit", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given unit A is registered + err := manager.Register(unitA) + require.NoError(t, err) + + // Given Unit B is not yet registered + // And Unit A depends on Unit B being started + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + + u := manager.Unit(unitB) + assert.Equal(t, unit.StatusNotRegistered, u.Status()) + + // Then: Unit A should not be ready, because it depends on Unit B + assert.False(t, manager.IsReady(unitA)) + + // When: Unit B is registered + err = manager.Register(unitB) + require.NoError(t, err) + + // Then: Unit A should still not be ready. + // Unit B is not registered, but it has not been started as required by the dependency. + assert.False(t, manager.IsReady(unitA)) + + // When: Unit B is started + err = manager.UpdateStatus(unitB, unit.StatusStarted) + require.NoError(t, err) + + // Then: Unit A should be ready, because its dependency is now in the desired state. + assert.True(t, manager.IsReady(unitA)) + }) + + t.Run("AddDependencyCreatesACyclicDependency", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Register units + err := manager.Register(unitA) + require.NoError(t, err) + err = manager.Register(unitB) + require.NoError(t, err) + err = manager.Register(unitC) + require.NoError(t, err) + err = manager.Register(unitD) + require.NoError(t, err) + + // A depends on B + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + // B depends on C + err = manager.AddDependency(unitB, unitC, unit.StatusStarted) + require.NoError(t, err) + + // C depends on D + err = manager.AddDependency(unitC, unitD, unit.StatusStarted) + require.NoError(t, err) + + // Try to make D depend on A (creates indirect cycle) + err = manager.AddDependency(unitD, unitA, unit.StatusStarted) + require.ErrorIs(t, err, unit.ErrCycleDetected) + }) +} + +func TestManager_UpdateStatus(t *testing.T) { + t.Parallel() + + t.Run("UpdateStatusTriggersReadinessRecalculation", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given units A and B are registered + err := manager.Register(unitA) + require.NoError(t, err) + err = manager.Register(unitB) + require.NoError(t, err) + + // Given Unit A depends on Unit B being unit.StatusStarted + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + + // Then: Unit A should not be ready (depends on B) + u := manager.Unit(unitA) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.False(t, manager.IsReady(unitA)) + + // When: Unit B is started + err = manager.UpdateStatus(unitB, unit.StatusStarted) + require.NoError(t, err) + + // Then: Unit A should be ready, because its dependency is now in the desired state. + u = manager.Unit(unitA) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.True(t, manager.IsReady(unitA)) + }) + + t.Run("UpdateStatusWithUnregisteredUnit", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given Unit A is not registered + // When: Unit A is updated to unit.StatusStarted + err := manager.UpdateStatus(unitA, unit.StatusStarted) + + // Then: a descriptive error communicates that the unit must be registered first. + require.ErrorIs(t, err, unit.ErrUnitNotFound) + }) + + t.Run("LinearChainDependencies", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given units A, B, and C are registered + err := manager.Register(unitA) + require.NoError(t, err) + err = manager.Register(unitB) + require.NoError(t, err) + err = manager.Register(unitC) + require.NoError(t, err) + + // Create chain: A depends on B being "started", B depends on C being "completed" + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + err = manager.AddDependency(unitB, unitC, unit.StatusComplete) + require.NoError(t, err) + + // Then: only Unit C should be ready (no dependencies) + u := manager.Unit(unitC) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.True(t, manager.IsReady(unitC)) + + u = manager.Unit(unitB) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.False(t, manager.IsReady(unitB)) + + u = manager.Unit(unitA) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.False(t, manager.IsReady(unitA)) + + // When: Unit C is completed + err = manager.UpdateStatus(unitC, unit.StatusComplete) + require.NoError(t, err) + + // Then: Unit B should be ready, because its dependency is now in the desired state. + u = manager.Unit(unitB) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.True(t, manager.IsReady(unitB)) + + u = manager.Unit(unitA) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.False(t, manager.IsReady(unitA)) + + u = manager.Unit(unitB) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.True(t, manager.IsReady(unitB)) + + // When: Unit B is started + err = manager.UpdateStatus(unitB, unit.StatusStarted) + require.NoError(t, err) + + // Then: Unit A should be ready, because its dependency is now in the desired state. + u = manager.Unit(unitA) + assert.Equal(t, unit.StatusPending, u.Status()) + assert.True(t, manager.IsReady(unitA)) + }) +} + +func TestManager_GetUnmetDependencies(t *testing.T) { + t.Parallel() + + t.Run("GetUnmetDependenciesForUnitWithNoDependencies", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given: Unit A is registered + err := manager.Register(unitA) + require.NoError(t, err) + + // Given: Unit A has no dependencies + // Then: Unit A should have no unmet dependencies + unmet, err := manager.GetUnmetDependencies(unitA) + require.NoError(t, err) + assert.Empty(t, unmet) + }) + + t.Run("GetUnmetDependenciesForUnitWithUnsatisfiedDependencies", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + err := manager.Register(unitA) + require.NoError(t, err) + err = manager.Register(unitB) + require.NoError(t, err) + + // Given: Unit A depends on Unit B being unit.StatusStarted + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + + unmet, err := manager.GetUnmetDependencies(unitA) + require.NoError(t, err) + require.Len(t, unmet, 1) + + assert.Equal(t, unitA, unmet[0].Unit) + assert.Equal(t, unitB, unmet[0].DependsOn) + assert.Equal(t, unit.StatusStarted, unmet[0].RequiredStatus) + assert.False(t, unmet[0].IsSatisfied) + }) + + t.Run("GetUnmetDependenciesForUnitWithSatisfiedDependencies", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given: Unit A and Unit B are registered + err := manager.Register(unitA) + require.NoError(t, err) + err = manager.Register(unitB) + require.NoError(t, err) + + // Given: Unit A depends on Unit B being unit.StatusStarted + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + + // When: Unit B is started + err = manager.UpdateStatus(unitB, unit.StatusStarted) + require.NoError(t, err) + + // Then: Unit A should have no unmet dependencies + unmet, err := manager.GetUnmetDependencies(unitA) + require.NoError(t, err) + assert.Empty(t, unmet) + }) + + t.Run("GetUnmetDependenciesForUnregisteredUnit", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // When: Unit A is requested + unmet, err := manager.GetUnmetDependencies(unitA) + + // Then: a descriptive error communicates that the unit must be registered first. + require.ErrorIs(t, err, unit.ErrUnitNotFound) + assert.Nil(t, unmet) + }) +} + +func TestManager_MultipleDependencies(t *testing.T) { + t.Parallel() + + t.Run("UnitWithMultipleDependencies", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Register all units + units := []unit.ID{unitA, unitB, unitC, unitD} + for _, unit := range units { + err := manager.Register(unit) + require.NoError(t, err) + } + + // A depends on B being unit.StatusStarted AND C being "started" + err := manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + err = manager.AddDependency(unitA, unitC, unit.StatusStarted) + require.NoError(t, err) + + // A should not be ready (depends on both B and C) + assert.False(t, manager.IsReady(unitA)) + + // Update B to unit.StatusStarted - A should still not be ready (needs C too) + err = manager.UpdateStatus(unitB, unit.StatusStarted) + require.NoError(t, err) + + assert.False(t, manager.IsReady(unitA)) + + // Update C to "started" - A should now be ready + err = manager.UpdateStatus(unitC, unit.StatusStarted) + require.NoError(t, err) + + assert.True(t, manager.IsReady(unitA)) + }) + + t.Run("ComplexDependencyChain", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Register all units + units := []unit.ID{unitA, unitB, unitC, unitD} + for _, unit := range units { + err := manager.Register(unit) + require.NoError(t, err) + } + + // Create complex dependency graph: + // A depends on B being unit.StatusStarted AND C being "started" + err := manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + err = manager.AddDependency(unitA, unitC, unit.StatusStarted) + require.NoError(t, err) + // B depends on D being "completed" + err = manager.AddDependency(unitB, unitD, unit.StatusComplete) + require.NoError(t, err) + // C depends on D being "completed" + err = manager.AddDependency(unitC, unitD, unit.StatusComplete) + require.NoError(t, err) + + // Initially only D is ready + assert.True(t, manager.IsReady(unitD)) + assert.False(t, manager.IsReady(unitB)) + assert.False(t, manager.IsReady(unitC)) + assert.False(t, manager.IsReady(unitA)) + + // Update D to "completed" - B and C should become ready + err = manager.UpdateStatus(unitD, unit.StatusComplete) + require.NoError(t, err) + + assert.True(t, manager.IsReady(unitB)) + assert.True(t, manager.IsReady(unitC)) + assert.False(t, manager.IsReady(unitA)) + + // Update B to unit.StatusStarted - A should still not be ready (needs C) + err = manager.UpdateStatus(unitB, unit.StatusStarted) + require.NoError(t, err) + + assert.False(t, manager.IsReady(unitA)) + + // Update C to "started" - A should now be ready + err = manager.UpdateStatus(unitC, unit.StatusStarted) + require.NoError(t, err) + + assert.True(t, manager.IsReady(unitA)) + }) + + t.Run("DifferentStatusTypes", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Register units + err := manager.Register(unitA) + require.NoError(t, err) + err = manager.Register(unitB) + require.NoError(t, err) + err = manager.Register(unitC) + require.NoError(t, err) + + // Given: Unit A depends on Unit B being unit.StatusStarted + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + // Given: Unit A depends on Unit C being "completed" + err = manager.AddDependency(unitA, unitC, unit.StatusComplete) + require.NoError(t, err) + + // When: Unit B is started + err = manager.UpdateStatus(unitB, unit.StatusStarted) + require.NoError(t, err) + + // Then: Unit A should not be ready, because only one of its dependencies is in the desired state. + // It still requires Unit C to be completed. + assert.False(t, manager.IsReady(unitA)) + + // When: Unit C is completed + err = manager.UpdateStatus(unitC, unit.StatusComplete) + require.NoError(t, err) + + // Then: Unit A should be ready, because both of its dependencies are in the desired state. + assert.True(t, manager.IsReady(unitA)) + }) +} + +func TestManager_IsReady(t *testing.T) { + t.Parallel() + + t.Run("IsReadyWithUnregisteredUnit", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Given: a unit is not registered + u := manager.Unit(unitA) + assert.Equal(t, unit.StatusNotRegistered, u.Status()) + // Then: the unit is not ready + assert.False(t, manager.IsReady(unitA)) + }) +} + +func TestManager_ToDOT(t *testing.T) { + t.Parallel() + + t.Run("ExportSimpleGraph", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Register units + err := manager.Register(unitA) + require.NoError(t, err) + err = manager.Register(unitB) + require.NoError(t, err) + + // Add dependency + err = manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + + dot, err := manager.ExportDOT("test") + require.NoError(t, err) + assert.NotEmpty(t, dot) + assert.Contains(t, dot, "digraph") + }) + + t.Run("ExportComplexGraph", func(t *testing.T) { + t.Parallel() + + manager := unit.NewManager() + + // Register all units + units := []unit.ID{unitA, unitB, unitC, unitD} + for _, unit := range units { + err := manager.Register(unit) + require.NoError(t, err) + } + + // Create complex dependency graph + // A depends on B and C, B depends on D, C depends on D + err := manager.AddDependency(unitA, unitB, unit.StatusStarted) + require.NoError(t, err) + err = manager.AddDependency(unitA, unitC, unit.StatusStarted) + require.NoError(t, err) + err = manager.AddDependency(unitB, unitD, unit.StatusComplete) + require.NoError(t, err) + err = manager.AddDependency(unitC, unitD, unit.StatusComplete) + require.NoError(t, err) + + dot, err := manager.ExportDOT("complex") + require.NoError(t, err) + assert.NotEmpty(t, dot) + assert.Contains(t, dot, "digraph") + }) +} From 17edeeaf04da172221978efd0797caa3be7db8b0 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Wed, 19 Nov 2025 18:17:12 +0000 Subject: [PATCH 177/255] fix(site): fix flaky Chromatic tests (#20808) ## Problem The `OrgsSortedAlphabetically` test from `OrganizationSidebarView.stories.tsx` was failing on Chromatic. Test logic was attempting to verify organization sorting order programmatically. This was identified in the Chromatic build of PR: https://www.chromatic.com/build?appId=624de63c6aacee003aa84340&number=26015 After fixing this test, two additional tests started failing: * `VanillaJavascriptError ` from `GlobalErrorBoundary.stories.tsx`: Test was making incorrect assertions about stack trace content * `MarkAllNotificationsAsReadError` from `NotificationsInbox.stories.tsx`: Test was flaky due to competing WebSocket error messages ## Solution These are Chromatic snapshot tests, so implementation details (like sorting order or exact error message content) are already validated by the visual snapshots. Programmatic assertions were causing flakiness and are redundant. --- .../GlobalErrorBoundary.stories.tsx | 14 +------------ .../OrganizationSidebarView.stories.tsx | 20 ------------------- .../NotificationsInbox.stories.tsx | 2 ++ 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/site/src/components/ErrorBoundary/GlobalErrorBoundary.stories.tsx b/site/src/components/ErrorBoundary/GlobalErrorBoundary.stories.tsx index c013b1cfa543e..c02b27c2da5b3 100644 --- a/site/src/components/ErrorBoundary/GlobalErrorBoundary.stories.tsx +++ b/site/src/components/ErrorBoundary/GlobalErrorBoundary.stories.tsx @@ -28,24 +28,12 @@ export const VanillaJavascriptError: Story = { args: { error: new Error("Something blew up :("), }, - play: async ({ canvasElement, args }) => { - const error = args.error as Error; + play: async ({ canvasElement }) => { const canvas = within(canvasElement); const showErrorButton = canvas.getByRole("button", { name: /Show error/i, }); await userEvent.click(showErrorButton); - - // Verify that error message content is now on screen; defer to - // accessible name queries as much as possible - canvas.getByRole("heading", { name: /Error/i }); - - const p = canvas.getByTestId("description"); - expect(p).toHaveTextContent(error.message); - - const codeBlock = canvas.getByTestId("code"); - expect(codeBlock).toHaveTextContent(error.name); - expect(codeBlock).toHaveTextContent(error.message); }, }; diff --git a/site/src/modules/management/OrganizationSidebarView.stories.tsx b/site/src/modules/management/OrganizationSidebarView.stories.tsx index ab4c2486d37c8..039b93406a0a5 100644 --- a/site/src/modules/management/OrganizationSidebarView.stories.tsx +++ b/site/src/modules/management/OrganizationSidebarView.stories.tsx @@ -243,26 +243,6 @@ export const OrgsSortedAlphabetically: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); await userEvent.click(canvas.getByRole("button", { name: /Omega org/i })); - - // dropdown is not in #storybook-root so must query full document - const globalScreen = within(document.body); - - await waitFor(() => { - expect(globalScreen.queryByText("alpha Org")).toBeInTheDocument(); - expect(globalScreen.queryByText("Zeta Org")).toBeInTheDocument(); - }); - - const orgElements = globalScreen.getAllByRole("option"); - // filter out Create btn - const filteredElems = orgElements.slice(0, 3); - - const orgNames = filteredElems.map( - // handling fuzzy matching - (el) => el.textContent?.replace(/^[A-Z]/, "").trim() || "", - ); - - // active name first - expect(orgNames).toEqual(["Omega org", "alpha Org", "Zeta Org"]); }, }; diff --git a/site/src/modules/notifications/NotificationsInbox/NotificationsInbox.stories.tsx b/site/src/modules/notifications/NotificationsInbox/NotificationsInbox.stories.tsx index f042ece2662de..d9f356a491b48 100644 --- a/site/src/modules/notifications/NotificationsInbox/NotificationsInbox.stories.tsx +++ b/site/src/modules/notifications/NotificationsInbox/NotificationsInbox.stories.tsx @@ -123,6 +123,8 @@ export const MarkAllAsReadFailure: Story = { name: /mark all as read/i, }); await userEvent.click(markAllAsReadButton); + // There have been some flakes here, with the socket erroring with + // "Unable to retrieve latest inbox notifications. Please try refreshing the browser." await body.findByText("Failed to mark all notifications as read"); }, }; From 753e1257580fc2dcaef0130f932c878a6baf5d47 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Wed, 19 Nov 2025 18:30:14 +0000 Subject: [PATCH 178/255] chore(site): add storybook stories for task initialization states (#20760) Adds a Storybook story to visualize the task initialization states (workspace pending/starting, agent connecting/starting) that were recently added. Screenshot 2025-11-17 at 18 58 55 Follow-up from PR: https://github.com/coder/coder/pull/20692 --- site/src/api/queries/templates.ts | 2 +- .../src/pages/TasksPage/TasksPage.stories.tsx | 17 +++++ site/src/testHelpers/entities.ts | 68 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/site/src/api/queries/templates.ts b/site/src/api/queries/templates.ts index 686611cb6cd41..da27333b0febe 100644 --- a/site/src/api/queries/templates.ts +++ b/site/src/api/queries/templates.ts @@ -35,7 +35,7 @@ export const templateByName = (organization: string, name: string) => { } satisfies QueryOptions