@app-vitals/agent-admin 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +43 -0
- package/README.md +72 -2
- package/e2e/agents-page.e2e.ts +338 -0
- package/e2e/chat-degraded.e2e.ts +153 -0
- package/e2e/chat-page.e2e.ts +169 -0
- package/e2e/chat-progress.e2e.ts +206 -0
- package/e2e/chat-thread-page.e2e.ts +444 -0
- package/e2e/login-page.e2e.ts +148 -0
- package/e2e/test-server.ts +427 -0
- package/openapi.json +6098 -0
- package/openapi.platform.json +5553 -0
- package/package.json +62 -4
- package/playwright.config.ts +25 -0
- package/prisma/client/browser.ts +134 -0
- package/prisma/client/client.ts +158 -0
- package/prisma/client/commonInputTypes.ts +578 -0
- package/prisma/client/enums.ts +15 -0
- package/prisma/client/internal/class.ts +424 -0
- package/prisma/client/internal/prismaNamespace.ts +2898 -0
- package/prisma/client/internal/prismaNamespaceBrowser.ts +476 -0
- package/prisma/client/models/Account.ts +1774 -0
- package/prisma/client/models/AccountInvite.ts +1386 -0
- package/prisma/client/models/AccountMember.ts +1351 -0
- package/prisma/client/models/Agent.ts +4554 -0
- package/prisma/client/models/AgentAsk.ts +1373 -0
- package/prisma/client/models/AgentChatTokenUsageDailyByModel.ts +1638 -0
- package/prisma/client/models/AgentCronJob.ts +2416 -0
- package/prisma/client/models/AgentCronRun.ts +2858 -0
- package/prisma/client/models/AgentCronRunModelBreakdown.ts +1532 -0
- package/prisma/client/models/AgentCronRunSkillUsage.ts +1642 -0
- package/prisma/client/models/AgentEnv.ts +1421 -0
- package/prisma/client/models/AgentGitHubInstallationsSnapshot.ts +1289 -0
- package/prisma/client/models/AgentMember.ts +1316 -0
- package/prisma/client/models/AgentPhaseMethodology.ts +1355 -0
- package/prisma/client/models/AgentPlugin.ts +1421 -0
- package/prisma/client/models/AgentToken.ts +1380 -0
- package/prisma/client/models/AgentTool.ts +1351 -0
- package/prisma/client/models/AgentWorkQueueSnapshot.ts +1297 -0
- package/prisma/client/models/ChatThreadWatch.ts +1183 -0
- package/prisma/client/models/PushSubscription.ts +1233 -0
- package/prisma/client/models/SessionAlertState.ts +1187 -0
- package/prisma/client/models/SessionFollow.ts +1187 -0
- package/prisma/client/models/UserNotificationPrefs.ts +1221 -0
- package/prisma/client/models.ts +34 -0
- package/prisma/migrations/20260608000000_init/migration.sql +112 -0
- package/prisma/migrations/20260616000000_agent_members/migration.sql +18 -0
- package/prisma/migrations/20260624000000_add_agent_self_hosted/migration.sql +2 -0
- package/prisma/migrations/20260624120000_add_agent_repos/migration.sql +3 -0
- package/prisma/migrations/20260625000000_add_agent_cron_run/migration.sql +26 -0
- package/prisma/migrations/20260626000000_add_cron_run_token_fields/migration.sql +10 -0
- package/prisma/migrations/20260627000000_add_agent_chat_token_usage_daily/migration.sql +18 -0
- package/prisma/migrations/20260627010000_add_agent_chat_token_usage_fk/migration.sql +2 -0
- package/prisma/migrations/20260701000000_add_chat_threads_messages/migration.sql +37 -0
- package/prisma/migrations/20260701010000_drop_chat_threads_messages/migration.sql +17 -0
- package/prisma/migrations/20260701020000_add_cron_run_model_breakdown/migration.sql +23 -0
- package/prisma/migrations/20260701020000_chat_token_by_model/migration.sql +25 -0
- package/prisma/migrations/20260701030000_add_secret_flag_to_agent_env/migration.sql +2 -0
- package/prisma/migrations/20260701030000_drop_cron_run_legacy_columns/migration.sql +5 -0
- package/prisma/migrations/20260706000000_consolidate_cron_run_token_accounting/migration.sql +54 -0
- package/prisma/migrations/20260709000000_add_phase_to_agent_cron_run/migration.sql +8 -0
- package/prisma/migrations/20260717000000_add_item_tracking_to_agent_cron_run/migration.sql +10 -0
- package/prisma/migrations/20260717010000_add_agent_work_queue_snapshot/migration.sql +20 -0
- package/prisma/migrations/20260718000000_add_cron_phase_columns/migration.sql +25 -0
- package/prisma/migrations/20260718100000_drop_agent_cron_run_phase_column/migration.sql +8 -0
- package/prisma/migrations/20260724000000_add_agent_type_name/migration.sql +3 -0
- package/prisma/migrations/20260725232433_add_agent_author_allowlist/migration.sql +2 -0
- package/prisma/migrations/20260727120000_add_not_null_to_agent_author_allowlist/migration.sql +3 -0
- package/prisma/migrations/20260731000000_add_session_id_to_agent_cron_run/migration.sql +2 -0
- package/prisma/migrations/20260821000000_add_agent_restrict_slack_to_members/migration.sql +3 -0
- package/prisma/migrations/20260901000000_add_push_subscription_and_chat_thread_watch/migration.sql +40 -0
- package/prisma/migrations/20260903000000_add_agent_patch_author_allowlist/migration.sql +2 -0
- package/prisma/migrations/20260903120000_add_agent_review_author_allowlist/migration.sql +18 -0
- package/prisma/migrations/20260903150000_drop_agent_author_allowlist/migration.sql +14 -0
- package/prisma/migrations/20260905000000_add_started_at_index_to_agent_cron_run/migration.sql +11 -0
- package/prisma/migrations/20260906000000_add_session_follow_notification_prefs_alert_state/migration.sql +47 -0
- package/prisma/migrations/20260911000000_backfill_auto_follow_since/migration.sql +16 -0
- package/prisma/migrations/20260923115814_add_last_heartbeat_at_to_agent_cron_run/migration.sql +2 -0
- package/prisma/migrations/20260924120000_add_trial_expiry_fields_to_agent/migration.sql +6 -0
- package/prisma/migrations/20260929000000_add_agent_phase_methodology/migration.sql +21 -0
- package/prisma/migrations/20260929120000_add_agent_policy_fields/migration.sql +15 -0
- package/prisma/migrations/20261005000000_add_agent_github_installations_snapshot/migration.sql +21 -0
- package/prisma/migrations/20261008000000_add_cron_run_telemetry/migration.sql +50 -0
- package/prisma/migrations/20261008010000_add_account_models/migration.sql +48 -0
- package/prisma/migrations/20261008020000_add_cron_lockdown_disabled_at/migration.sql +3 -0
- package/prisma/migrations/20261009000000_chat_token_usage_bigint/migration.sql +7 -0
- package/prisma/migrations/20261010000000_add_cron_run_command_variant/migration.sql +2 -0
- package/prisma/migrations/20261019000000_add_agent_anthropic_identity/migration.sql +9 -0
- package/prisma/migrations/20261025000000_add_agent_ask/migration.sql +23 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema/coding.prisma +85 -0
- package/prisma/schema/schema.prisma +506 -0
- package/prisma.config.ts +34 -0
- package/pwa-assets/icons/apple-touch-icon.png +0 -0
- package/pwa-assets/icons/favicon-32.png +0 -0
- package/pwa-assets/icons/icon-192.png +0 -0
- package/pwa-assets/icons/icon-512.png +0 -0
- package/pwa-assets/icons/icon-maskable-192.png +0 -0
- package/pwa-assets/icons/icon-maskable-512.png +0 -0
- package/src/account-agent-create.ts +42 -0
- package/src/account-agent-create.unit.test.ts +28 -0
- package/src/account-created-notifier.ts +71 -0
- package/src/account-created-notifier.unit.test.ts +70 -0
- package/src/account-email.ts +9 -0
- package/src/account-email.unit.test.ts +21 -0
- package/src/account-invites.ts +70 -0
- package/src/account-lifecycle.integration.test.ts +329 -0
- package/src/account-lifecycle.ts +206 -0
- package/src/account-lifecycle.unit.test.ts +108 -0
- package/src/account-members.ts +128 -0
- package/src/account-onboarding.integration.test.ts +101 -0
- package/src/account-onboarding.ts +95 -0
- package/src/account-trial-expiry-sweeper.ts +85 -0
- package/src/account-trial-warning-sweeper.integration.test.ts +228 -0
- package/src/account-trial-warning-sweeper.ts +216 -0
- package/src/accounts-api.integration.test.ts +196 -0
- package/src/accounts-api.smoke.test.ts +292 -0
- package/src/accounts-api.ts +289 -0
- package/src/accounts-api.unit.test.ts +93 -0
- package/src/accounts.integration.test.ts +178 -0
- package/src/accounts.ts +208 -0
- package/src/admin-boot-config.ts +574 -0
- package/src/admin-boot-config.unit.test.ts +28 -0
- package/src/admin-boot.boot-order.unit.test.ts +91 -0
- package/src/admin-boot.ts +690 -0
- package/src/admin-dev-login.smoke.test.ts +317 -0
- package/src/admin-extension.ts +432 -0
- package/src/admin-extension.unit.test.ts +532 -0
- package/src/admin-local-agent.smoke.test.ts +1643 -0
- package/src/admin-spec.smoke.test.ts +96 -0
- package/src/admin-spec.ts +186 -0
- package/src/admin-ui-account-nav.ts +20 -0
- package/src/admin-ui-account-pages.ts +131 -0
- package/src/admin-ui-account-pages.unit.test.ts +112 -0
- package/src/admin-ui-account.smoke.test.ts +286 -0
- package/src/admin-ui-account.ts +185 -0
- package/src/admin-ui-accounts-pages.ts +202 -0
- package/src/admin-ui-accounts-pages.unit.test.ts +138 -0
- package/src/admin-ui-accounts.smoke.test.ts +434 -0
- package/src/admin-ui-accounts.ts +266 -0
- package/src/admin-ui-layout.ts +60 -0
- package/src/admin-ui-layout.unit.test.ts +140 -0
- package/src/admin-ui-pages.ts +3489 -0
- package/src/admin-ui-pages.unit.test.ts +4355 -0
- package/src/admin-ui-push-test.smoke.test.ts +125 -0
- package/src/admin-ui-push-test.ts +102 -0
- package/src/admin-ui-styles.ts +656 -0
- package/src/admin-ui-styles.unit.test.ts +103 -0
- package/src/admin-ui.fixtures.ts +457 -0
- package/src/admin-ui.smoke.test.ts +7227 -0
- package/src/admin-ui.ts +3824 -0
- package/src/agent-account-assignment.ts +49 -0
- package/src/agent-account-assignment.unit.test.ts +99 -0
- package/src/agent-anthropic-identity.integration.test.ts +99 -0
- package/src/agent-anthropic-identity.ts +78 -0
- package/src/agent-anthropic-identity.unit.test.ts +77 -0
- package/src/agent-ask-config.ts +59 -0
- package/src/agent-ask-config.unit.test.ts +70 -0
- package/src/agent-ask.integration.test.ts +175 -0
- package/src/agent-ask.smoke.test.ts +457 -0
- package/src/agent-ask.ts +378 -0
- package/src/agent-chat-tokens.integration.test.ts +420 -0
- package/src/agent-chat-tokens.smoke.test.ts +551 -0
- package/src/agent-chat-tokens.ts +259 -0
- package/src/agent-cron-jobs.integration.test.ts +582 -0
- package/src/agent-cron-jobs.ts +615 -0
- package/src/agent-cron-jobs.unit.test.ts +420 -0
- package/src/agent-cron-run-stats.integration.test.ts +1551 -0
- package/src/agent-cron-run-stats.smoke.test.ts +953 -0
- package/src/agent-cron-run-stats.ts +752 -0
- package/src/agent-cron-runs.integration.test.ts +1491 -0
- package/src/agent-cron-runs.ts +489 -0
- package/src/agent-deletion-checklist.ts +128 -0
- package/src/agent-deletion-checklist.unit.test.ts +201 -0
- package/src/agent-deletion-per-agent.integration.test.ts +323 -0
- package/src/agent-deletion.integration.test.ts +323 -0
- package/src/agent-deletion.ts +329 -0
- package/src/agent-deletion.unit.test.ts +412 -0
- package/src/agent-envs.integration.test.ts +323 -0
- package/src/agent-envs.ts +244 -0
- package/src/agent-github-installations.integration.test.ts +115 -0
- package/src/agent-github-installations.ts +52 -0
- package/src/agent-manifest.ts +685 -0
- package/src/agent-manifest.unit.test.ts +870 -0
- package/src/agent-members.ts +69 -0
- package/src/agent-members.unit.test.ts +250 -0
- package/src/agent-patch-author-allowlist.integration.test.ts +218 -0
- package/src/agent-plugins.integration.test.ts +157 -0
- package/src/agent-plugins.ts +95 -0
- package/src/agent-plugins.unit.test.ts +43 -0
- package/src/agent-provisioner-per-agent.integration.test.ts +949 -0
- package/src/agent-provisioner.integration.test.ts +1245 -0
- package/src/agent-provisioner.ts +1650 -0
- package/src/agent-provisioner.unit.test.ts +1852 -0
- package/src/agent-tokens.integration.test.ts +150 -0
- package/src/agent-tokens.ts +112 -0
- package/src/agent-tools.integration.test.ts +129 -0
- package/src/agent-tools.ts +111 -0
- package/src/agents-api.integration.test.ts +705 -0
- package/src/agents-api.sentry.smoke.test.ts +478 -0
- package/src/agents-api.smoke.test.ts +5986 -0
- package/src/agents-api.ts +2826 -0
- package/src/agents.integration.test.ts +512 -0
- package/src/agents.ts +1097 -0
- package/src/agents.unit.test.ts +1736 -0
- package/src/anthropic-credential-guard.ts +44 -0
- package/src/anthropic-credential-guard.unit.test.ts +88 -0
- package/src/anthropic-federated-token-provider.integration.test.ts +248 -0
- package/src/anthropic-federated-token-provider.ts +249 -0
- package/src/anthropic-identity-client.fixtures.ts +237 -0
- package/src/anthropic-identity-client.integration.test.ts +342 -0
- package/src/anthropic-identity-client.ts +473 -0
- package/src/anthropic-identity-fixture-spec.integration.test.ts +169 -0
- package/src/anthropic-identity-name.integration.test.ts +83 -0
- package/src/anthropic-identity-name.ts +163 -0
- package/src/anthropic-identity-name.unit.test.ts +345 -0
- package/src/anthropic-openapi-contract.fixtures.ts +192 -0
- package/src/api-auth.ts +383 -0
- package/src/api-auth.unit.test.ts +837 -0
- package/src/api.smoke.test.ts +1225 -0
- package/src/api.ts +376 -0
- package/src/attachment-validation.ts +124 -0
- package/src/attachment-validation.unit.test.ts +218 -0
- package/src/caller-scope.ts +131 -0
- package/src/caller-scope.unit.test.ts +116 -0
- package/src/chat-credential-config.ts +22 -0
- package/src/chat-credentials-logging.unit.test.ts +92 -0
- package/src/chat-markers.ts +114 -0
- package/src/chat-markers.unit.test.ts +116 -0
- package/src/chat-service-provisioning-client.integration.test.ts +412 -0
- package/src/chat-service-provisioning-client.ts +221 -0
- package/src/chat.smoke.test.ts +1875 -0
- package/src/clock.ts +47 -0
- package/src/coding-task-store-provisioning-client.integration.test.ts +372 -0
- package/src/coding-task-store-provisioning-client.ts +156 -0
- package/src/crypto.ts +83 -0
- package/src/dev-auth-guard.ts +29 -0
- package/src/dev-auth-guard.unit.test.ts +51 -0
- package/src/errors.ts +77 -0
- package/src/errors.unit.test.ts +52 -0
- package/src/fixtures/anthropic-identity-recorded-fixtures.json +850 -0
- package/src/fixtures/anthropic-openapi/SDK_VERSION +1 -0
- package/src/fixtures/anthropic-openapi/mock-spec.json.gz +0 -0
- package/src/fixtures/chat-service-provisioning-cassette.json +156 -0
- package/src/fixtures/github-app-manifest-cassette.json +44 -0
- package/src/fixtures/google-auth-cassette.json +36 -0
- package/src/fixtures/http-chat-client-cassette.json +226 -0
- package/src/fixtures/k8s-cassette.json +470 -0
- package/src/fixtures/okta-auth-cassette.json +36 -0
- package/src/fixtures/slack-provision-cassette.json +12 -0
- package/src/fixtures/slack-provisioning-http-cassette.json +98 -0
- package/src/fixtures/task-store-provisioning-cassette.json +85 -0
- package/src/github-app-provisioning-client.integration.test.ts +149 -0
- package/src/github-app-provisioning-client.ts +161 -0
- package/src/github-app-provisioning-client.unit.test.ts +114 -0
- package/src/github-provisioning-service.ts +569 -0
- package/src/github-provisioning-service.unit.test.ts +123 -0
- package/src/google-auth-client.integration.test.ts +195 -0
- package/src/google-auth-client.ts +121 -0
- package/src/http-chat-client.integration.test.ts +554 -0
- package/src/http-chat-client.ts +533 -0
- package/src/http-chat-client.unit.test.ts +119 -0
- package/src/import-boundary.unit.test.ts +240 -0
- package/src/kubernetes-client.integration.test.ts +697 -0
- package/src/kubernetes-client.ts +1027 -0
- package/src/kubernetes-client.unit.test.ts +726 -0
- package/src/main.smoke.test.ts +91 -0
- package/src/main.ts +21 -0
- package/src/main.unit.test.ts +801 -0
- package/src/okta-auth-client.integration.test.ts +263 -0
- package/src/okta-auth-client.ts +152 -0
- package/src/openapi-schemas.ts +1310 -0
- package/src/openapi-schemas.unit.test.ts +348 -0
- package/src/per-agent-identity.fixtures.ts +305 -0
- package/src/platform-doc-route.ts +40 -0
- package/src/prisma-client.integration.test.ts +57 -0
- package/src/prisma-client.ts +187 -0
- package/src/prisma-client.unit.test.ts +178 -0
- package/src/prisma-tx.ts +23 -0
- package/src/push-content.ts +177 -0
- package/src/push-content.unit.test.ts +213 -0
- package/src/push-crypto.ts +135 -0
- package/src/push-crypto.unit.test.ts +66 -0
- package/src/push-sender.ts +169 -0
- package/src/push-sender.unit.test.ts +107 -0
- package/src/push-service.ts +332 -0
- package/src/push-service.unit.test.ts +679 -0
- package/src/push-toggle.ts +135 -0
- package/src/push-toggle.unit.test.ts +78 -0
- package/src/pwa-https-gating.smoke.test.ts +229 -0
- package/src/pwa-icons.unit.test.ts +41 -0
- package/src/pwa-routes.smoke.test.ts +276 -0
- package/src/pwa.ts +380 -0
- package/src/pwa.unit.test.ts +321 -0
- package/src/schema.unit.test.ts +122 -0
- package/src/self-serve-config.ts +52 -0
- package/src/self-serve-config.unit.test.ts +79 -0
- package/src/server.smoke.test.ts +273 -0
- package/src/server.ts +50 -0
- package/src/service-integration.ts +67 -0
- package/src/service-integration.unit.test.ts +129 -0
- package/src/slack-provisioning-client.integration.test.ts +308 -0
- package/src/slack-provisioning-client.ts +411 -0
- package/src/slack-provisioning-client.unit.test.ts +80 -0
- package/src/slack-provisioning-service.integration.test.ts +637 -0
- package/src/slack-provisioning-service.ts +451 -0
- package/src/slack-provisioning.integration.test.ts +395 -0
- package/src/specialty-manifest-loader.fixture.integration.test.ts +63 -0
- package/src/specialty-manifest-loader.ts +255 -0
- package/src/specialty-manifest-loader.unit.test.ts +301 -0
- package/src/specialty-registry.schema.unit.test.ts +35 -0
- package/src/specialty-registry.ts +236 -0
- package/src/specialty-registry.unit.test.ts +272 -0
- package/src/startup-policy-reconcile.ts +77 -0
- package/src/startup-policy-reconcile.unit.test.ts +251 -0
- package/src/theme-tokens.json +12 -0
- package/src/token-crypto.ts +60 -0
- package/src/token-crypto.unit.test.ts +182 -0
- package/src/trial-expiry-sweeper.integration.test.ts +318 -0
- package/src/trial-expiry-sweeper.ts +132 -0
- package/src/trial-expiry-sweeper.unit.test.ts +391 -0
- package/src/trial-expiry-warning-sweeper.integration.test.ts +468 -0
- package/src/trial-expiry-warning-sweeper.ts +447 -0
- package/src/trial-expiry-warning-sweeper.unit.test.ts +148 -0
- package/src/voice-contract.unit.test.ts +102 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/agent-deletion-checklist.unit.test.ts
|
|
3
|
+
* Unit tests for the pure agent-deletion manual-steps checklist builder. No
|
|
4
|
+
* I/O, no network, no fs — these assert output shape/content only.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, it } from "bun:test";
|
|
8
|
+
import {
|
|
9
|
+
type ManualStep,
|
|
10
|
+
buildManualStepsChecklist,
|
|
11
|
+
} from "./agent-deletion-checklist.ts";
|
|
12
|
+
|
|
13
|
+
describe("buildManualStepsChecklist — named keys", () => {
|
|
14
|
+
it("produces a specific instruction for GH_TOKEN", () => {
|
|
15
|
+
const steps = buildManualStepsChecklist([
|
|
16
|
+
{ key: "GH_TOKEN", secret: true },
|
|
17
|
+
]);
|
|
18
|
+
expect(steps).toHaveLength(1);
|
|
19
|
+
expect(steps[0].key).toBe("GH_TOKEN");
|
|
20
|
+
expect(steps[0].message).toContain("github.com/settings/tokens");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("produces a specific instruction for ANTHROPIC_API_KEY", () => {
|
|
24
|
+
const steps = buildManualStepsChecklist([
|
|
25
|
+
{ key: "ANTHROPIC_API_KEY", secret: true },
|
|
26
|
+
]);
|
|
27
|
+
expect(steps).toHaveLength(1);
|
|
28
|
+
expect(steps[0].key).toBe("ANTHROPIC_API_KEY");
|
|
29
|
+
expect(steps[0].message.toLowerCase()).toContain("anthropic console");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("produces a specific instruction for CLAUDE_CODE_OAUTH_TOKEN", () => {
|
|
33
|
+
const steps = buildManualStepsChecklist([
|
|
34
|
+
{ key: "CLAUDE_CODE_OAUTH_TOKEN", secret: true },
|
|
35
|
+
]);
|
|
36
|
+
expect(steps).toHaveLength(1);
|
|
37
|
+
expect(steps[0].key).toBe("CLAUDE_CODE_OAUTH_TOKEN");
|
|
38
|
+
expect(steps[0].message).toContain("claude auth logout");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("buildManualStepsChecklist — excluded Slack keys", () => {
|
|
43
|
+
it("produces no checklist entry for SLACK_APP_ID, SLACK_SIGNING_SECRET, SLACK_BOT_TOKEN even when secret: true", () => {
|
|
44
|
+
const steps = buildManualStepsChecklist([
|
|
45
|
+
{ key: "SLACK_APP_ID", secret: true },
|
|
46
|
+
{ key: "SLACK_SIGNING_SECRET", secret: true },
|
|
47
|
+
{ key: "SLACK_BOT_TOKEN", secret: true },
|
|
48
|
+
]);
|
|
49
|
+
expect(steps).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("buildManualStepsChecklist — generic/custom secrets", () => {
|
|
54
|
+
it("produces the generic message for LINEAR_API_KEY", () => {
|
|
55
|
+
const steps = buildManualStepsChecklist([
|
|
56
|
+
{ key: "LINEAR_API_KEY", secret: true },
|
|
57
|
+
]);
|
|
58
|
+
expect(steps).toHaveLength(1);
|
|
59
|
+
expect(steps[0].key).toBe("LINEAR_API_KEY");
|
|
60
|
+
expect(steps[0].message).toBe(
|
|
61
|
+
"Custom secret 'LINEAR_API_KEY' was added manually and has no automated revocation — verify whether it needs to be revoked at the source.",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("produces the generic message for STRIPE_API_KEY", () => {
|
|
66
|
+
const steps = buildManualStepsChecklist([
|
|
67
|
+
{ key: "STRIPE_API_KEY", secret: true },
|
|
68
|
+
]);
|
|
69
|
+
expect(steps).toHaveLength(1);
|
|
70
|
+
expect(steps[0].key).toBe("STRIPE_API_KEY");
|
|
71
|
+
expect(steps[0].message).toContain("STRIPE_API_KEY");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("produces the generic message for an arbitrary unknown custom secret key", () => {
|
|
75
|
+
const steps = buildManualStepsChecklist([
|
|
76
|
+
{ key: "SOME_FUTURE_SECRET", secret: true },
|
|
77
|
+
]);
|
|
78
|
+
expect(steps).toHaveLength(1);
|
|
79
|
+
expect(steps[0].key).toBe("SOME_FUTURE_SECRET");
|
|
80
|
+
expect(steps[0].message).toContain("SOME_FUTURE_SECRET");
|
|
81
|
+
expect(steps[0].message).toContain(
|
|
82
|
+
"has no automated revocation — verify whether it needs to be revoked at the source.",
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("buildManualStepsChecklist — non-secret rows", () => {
|
|
88
|
+
it("produces no checklist entry for secret: false rows regardless of key name", () => {
|
|
89
|
+
const steps = buildManualStepsChecklist([
|
|
90
|
+
{ key: "GH_TOKEN", secret: false },
|
|
91
|
+
{ key: "LINEAR_API_KEY", secret: false },
|
|
92
|
+
{ key: "SOME_RANDOM_KEY", secret: false },
|
|
93
|
+
]);
|
|
94
|
+
expect(steps).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("buildManualStepsChecklist — general behavior", () => {
|
|
99
|
+
it("returns an empty array for an empty envRows array", () => {
|
|
100
|
+
expect(buildManualStepsChecklist([])).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("processes a mixed set of rows end-to-end", () => {
|
|
104
|
+
const steps = buildManualStepsChecklist([
|
|
105
|
+
{ key: "GH_TOKEN", secret: true },
|
|
106
|
+
{ key: "ANTHROPIC_API_KEY", secret: true },
|
|
107
|
+
{ key: "CLAUDE_CODE_OAUTH_TOKEN", secret: true },
|
|
108
|
+
{ key: "SLACK_APP_ID", secret: true },
|
|
109
|
+
{ key: "SLACK_SIGNING_SECRET", secret: true },
|
|
110
|
+
{ key: "SLACK_BOT_TOKEN", secret: true },
|
|
111
|
+
{ key: "LINEAR_API_KEY", secret: true },
|
|
112
|
+
{ key: "STRIPE_API_KEY", secret: true },
|
|
113
|
+
{ key: "NON_SECRET_VALUE", secret: false },
|
|
114
|
+
]);
|
|
115
|
+
const keys = steps.map((s) => s.key);
|
|
116
|
+
expect(keys).toEqual([
|
|
117
|
+
"GH_TOKEN",
|
|
118
|
+
"ANTHROPIC_API_KEY",
|
|
119
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
120
|
+
"LINEAR_API_KEY",
|
|
121
|
+
"STRIPE_API_KEY",
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("preserves each entry's key on the returned ManualStep", () => {
|
|
126
|
+
const steps: ManualStep[] = buildManualStepsChecklist([
|
|
127
|
+
{ key: "GH_TOKEN", secret: true },
|
|
128
|
+
]);
|
|
129
|
+
expect(steps[0]).toMatchObject({ key: "GH_TOKEN" });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("treats key matching as case-sensitive exact match (lowercase gh_token is treated as a generic custom secret)", () => {
|
|
133
|
+
const steps = buildManualStepsChecklist([
|
|
134
|
+
{ key: "gh_token", secret: true },
|
|
135
|
+
]);
|
|
136
|
+
expect(steps).toHaveLength(1);
|
|
137
|
+
expect(steps[0].message).toContain("gh_token");
|
|
138
|
+
expect(steps[0].message).not.toContain("github.com/settings/tokens");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("produces one entry per row when the same secret key appears twice (duplicates are not deduplicated)", () => {
|
|
142
|
+
const steps = buildManualStepsChecklist([
|
|
143
|
+
{ key: "GH_TOKEN", secret: true },
|
|
144
|
+
{ key: "GH_TOKEN", secret: true },
|
|
145
|
+
]);
|
|
146
|
+
expect(steps).toHaveLength(2);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("buildManualStepsChecklist — leftover Anthropic identity", () => {
|
|
151
|
+
const ALL = {
|
|
152
|
+
anthropicFederationRuleId: "fdrl_1",
|
|
153
|
+
anthropicServiceAccountId: "svac_1",
|
|
154
|
+
k8sServiceAccountName: "agent-sa",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
it("lists rule, Anthropic service account, then Kubernetes ServiceAccount in removal order", () => {
|
|
158
|
+
const steps = buildManualStepsChecklist([], ALL);
|
|
159
|
+
expect(steps.map((s) => s.key)).toEqual([
|
|
160
|
+
"anthropicFederationRuleId",
|
|
161
|
+
"anthropicServiceAccountId",
|
|
162
|
+
"k8sServiceAccountName",
|
|
163
|
+
]);
|
|
164
|
+
expect(steps[0]?.message).toContain("fdrl_1");
|
|
165
|
+
expect(steps[1]?.message).toContain("svac_1");
|
|
166
|
+
expect(steps[1]?.message).toContain("rule");
|
|
167
|
+
expect(steps[2]?.message).toContain("agent-sa");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("lists only the pieces still present", () => {
|
|
171
|
+
const steps = buildManualStepsChecklist([], {
|
|
172
|
+
anthropicFederationRuleId: null,
|
|
173
|
+
anthropicServiceAccountId: "svac_1",
|
|
174
|
+
k8sServiceAccountName: "agent-sa",
|
|
175
|
+
});
|
|
176
|
+
expect(steps.map((s) => s.key)).toEqual([
|
|
177
|
+
"anthropicServiceAccountId",
|
|
178
|
+
"k8sServiceAccountName",
|
|
179
|
+
]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("adds nothing when every piece is gone or the argument is omitted", () => {
|
|
183
|
+
expect(
|
|
184
|
+
buildManualStepsChecklist([], {
|
|
185
|
+
anthropicFederationRuleId: null,
|
|
186
|
+
anthropicServiceAccountId: null,
|
|
187
|
+
k8sServiceAccountName: null,
|
|
188
|
+
}),
|
|
189
|
+
).toEqual([]);
|
|
190
|
+
expect(buildManualStepsChecklist([])).toEqual([]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("appends identity entries after the secret entries", () => {
|
|
194
|
+
const steps = buildManualStepsChecklist(
|
|
195
|
+
[{ key: "GH_TOKEN", secret: true }],
|
|
196
|
+
ALL,
|
|
197
|
+
);
|
|
198
|
+
expect(steps[0]?.key).toBe("GH_TOKEN");
|
|
199
|
+
expect(steps).toHaveLength(4);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* admin/src/agent-deletion-per-agent.integration.test.ts
|
|
3
|
+
* Integration tests for deleteAgentFully() against a real
|
|
4
|
+
* KubernetesAgentProvisioner in perAgent identity mode: the anthropic-identity
|
|
5
|
+
* step archives the federation rule, then the Anthropic service account, then
|
|
6
|
+
* deletes the Kubernetes ServiceAccount, keeping the Agent row (and listing
|
|
7
|
+
* what is left in the manual-steps checklist) when any piece fails.
|
|
8
|
+
*
|
|
9
|
+
* The agent is first provisioned for real so the identity it deletes is the one
|
|
10
|
+
* provisioning created. Doubles are injected — RecordedKubernetesClient (wrapped
|
|
11
|
+
* to fail on demand), FakeAnthropicIdentityClient, an in-memory identity store
|
|
12
|
+
* and a tiny in-memory Prisma — so no DB, cluster or network is needed and no
|
|
13
|
+
* globals are touched.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { beforeEach, describe, expect, it } from "bun:test";
|
|
17
|
+
import type { AgentAnthropicIdentity } from "./agent-anthropic-identity.ts";
|
|
18
|
+
import {
|
|
19
|
+
type DeleteAgentFullyDeps,
|
|
20
|
+
deleteAgentFully,
|
|
21
|
+
} from "./agent-deletion.ts";
|
|
22
|
+
import { sanitizeAgentName } from "./agent-manifest.ts";
|
|
23
|
+
import {
|
|
24
|
+
KubernetesAgentProvisioner,
|
|
25
|
+
type KubernetesAgentProvisionerConfig,
|
|
26
|
+
} from "./agent-provisioner.ts";
|
|
27
|
+
import type { AgentTokenService } from "./agent-tokens.ts";
|
|
28
|
+
import { AnthropicApiError } from "./anthropic-identity-client.ts";
|
|
29
|
+
import { RecordedKubernetesClient } from "./kubernetes-client.ts";
|
|
30
|
+
import {
|
|
31
|
+
FakeAnthropicIdentityClient,
|
|
32
|
+
faultyKubernetesClient,
|
|
33
|
+
InMemoryAgentIdentityStore,
|
|
34
|
+
} from "./per-agent-identity.fixtures.ts";
|
|
35
|
+
|
|
36
|
+
const NAMESPACE = "agents";
|
|
37
|
+
const AGENT_ID = "cmqalfjcm000m4101iharq28k";
|
|
38
|
+
const AGENT_NAME = "dude-bot";
|
|
39
|
+
const RESOURCE = sanitizeAgentName(AGENT_ID);
|
|
40
|
+
const OWNER = "jane.doe@example.com";
|
|
41
|
+
|
|
42
|
+
const BASE_CONFIG: KubernetesAgentProvisionerConfig = {
|
|
43
|
+
namespace: NAMESPACE,
|
|
44
|
+
image: "ghcr.io/example/shipwright-agent",
|
|
45
|
+
imageTag: "v1.0.0",
|
|
46
|
+
apiUrl: "http://shipwright-admin.shipwright.svc:3001",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function stubTokens(): AgentTokenService {
|
|
50
|
+
return {
|
|
51
|
+
create: async (agentId: string) => ({
|
|
52
|
+
id: `tok-${agentId}`,
|
|
53
|
+
rawToken: `raw-${agentId}`,
|
|
54
|
+
}),
|
|
55
|
+
} as unknown as AgentTokenService;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Prisma double whose Agent identity columns live in the identity store. */
|
|
59
|
+
class StoreBackedPrisma {
|
|
60
|
+
deleted = false;
|
|
61
|
+
constructor(private readonly store: InMemoryAgentIdentityStore) {}
|
|
62
|
+
|
|
63
|
+
agent = {
|
|
64
|
+
findUnique: async (_args: {
|
|
65
|
+
where: { id: string };
|
|
66
|
+
select: {
|
|
67
|
+
id: true;
|
|
68
|
+
name: true;
|
|
69
|
+
anthropicFederationRuleId: true;
|
|
70
|
+
anthropicServiceAccountId: true;
|
|
71
|
+
k8sServiceAccountName: true;
|
|
72
|
+
};
|
|
73
|
+
}) => {
|
|
74
|
+
const row = this.store.rows.get(AGENT_ID);
|
|
75
|
+
if (this.deleted || !row) return null;
|
|
76
|
+
return {
|
|
77
|
+
id: AGENT_ID,
|
|
78
|
+
name: AGENT_NAME,
|
|
79
|
+
anthropicFederationRuleId: row.anthropicFederationRuleId,
|
|
80
|
+
anthropicServiceAccountId: row.anthropicServiceAccountId,
|
|
81
|
+
k8sServiceAccountName: row.k8sServiceAccountName,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
delete: async (_args: { where: { id: string } }): Promise<unknown> => {
|
|
85
|
+
this.deleted = true;
|
|
86
|
+
return {};
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
agentEnv = {
|
|
91
|
+
findMany: async (_args: {
|
|
92
|
+
where: { agentId: string };
|
|
93
|
+
select: { key: true; value: true; secret: true };
|
|
94
|
+
}): Promise<{ key: string; value: string; secret: boolean }[]> => [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface Harness {
|
|
99
|
+
events: string[];
|
|
100
|
+
recorded: RecordedKubernetesClient;
|
|
101
|
+
k8s: ReturnType<typeof faultyKubernetesClient>;
|
|
102
|
+
anthropic: FakeAnthropicIdentityClient;
|
|
103
|
+
store: InMemoryAgentIdentityStore;
|
|
104
|
+
prisma: StoreBackedPrisma;
|
|
105
|
+
provisioner: KubernetesAgentProvisioner;
|
|
106
|
+
deps: DeleteAgentFullyDeps;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function harness(opts: { perAgent?: boolean } = {}): Harness {
|
|
110
|
+
const events: string[] = [];
|
|
111
|
+
const recorded = new RecordedKubernetesClient({
|
|
112
|
+
deployments: {},
|
|
113
|
+
secrets: {},
|
|
114
|
+
pvcs: {},
|
|
115
|
+
serviceAccounts: {},
|
|
116
|
+
});
|
|
117
|
+
const k8s = faultyKubernetesClient(recorded, events);
|
|
118
|
+
const anthropic = new FakeAnthropicIdentityClient(events);
|
|
119
|
+
const store = new InMemoryAgentIdentityStore(events);
|
|
120
|
+
const perAgent = opts.perAgent ?? true;
|
|
121
|
+
const provisioner = new KubernetesAgentProvisioner(k8s, stubTokens(), {
|
|
122
|
+
...BASE_CONFIG,
|
|
123
|
+
...(perAgent
|
|
124
|
+
? {
|
|
125
|
+
perAgentIdentity: {
|
|
126
|
+
client: anthropic,
|
|
127
|
+
identities: store,
|
|
128
|
+
names: store,
|
|
129
|
+
organizationId: "org-1",
|
|
130
|
+
workspaceId: "wrkspc_1",
|
|
131
|
+
issuerId: "fdis_cluster",
|
|
132
|
+
audience: "https://api.anthropic.com",
|
|
133
|
+
expirationSeconds: 600,
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
: {}),
|
|
137
|
+
});
|
|
138
|
+
const prisma = new StoreBackedPrisma(store);
|
|
139
|
+
const deps: DeleteAgentFullyDeps = {
|
|
140
|
+
prisma,
|
|
141
|
+
provisioner,
|
|
142
|
+
serviceIntegrations: [
|
|
143
|
+
{
|
|
144
|
+
name: "task-store",
|
|
145
|
+
listTokensForAgent: async () => [],
|
|
146
|
+
revokeToken: async () => {},
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
chatService: {
|
|
150
|
+
listTokensForAgent: async () => [],
|
|
151
|
+
revokeToken: async () => {},
|
|
152
|
+
deleteThreadsForAgent: async () => ({ deleted: 0 }),
|
|
153
|
+
},
|
|
154
|
+
slack: { deleteApp: async () => {} },
|
|
155
|
+
decrypt: (value) => value,
|
|
156
|
+
};
|
|
157
|
+
return { events, recorded, k8s, anthropic, store, prisma, provisioner, deps };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Provision the agent for real, then forget the provisioning events. */
|
|
161
|
+
async function provisioned(h: Harness): Promise<void> {
|
|
162
|
+
h.store.addAgent(AGENT_ID, { ownerEmail: OWNER });
|
|
163
|
+
await h.provisioner.provision(AGENT_ID, { slug: AGENT_NAME });
|
|
164
|
+
h.events.length = 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function exists(p: Promise<unknown>): Promise<boolean> {
|
|
168
|
+
try {
|
|
169
|
+
await p;
|
|
170
|
+
return true;
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function identityOf(h: Harness): AgentAnthropicIdentity {
|
|
177
|
+
return h.store.rows.get(AGENT_ID) as AgentAnthropicIdentity;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
describe("deleteAgentFully — per-agent Anthropic identity", () => {
|
|
181
|
+
let h: Harness;
|
|
182
|
+
|
|
183
|
+
beforeEach(async () => {
|
|
184
|
+
h = harness();
|
|
185
|
+
await provisioned(h);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("removes the rule, then the Anthropic service account, then the K8s ServiceAccount, clearing each ID, and deletes the row", async () => {
|
|
189
|
+
const result = await deleteAgentFully(AGENT_ID, h.deps);
|
|
190
|
+
|
|
191
|
+
expect(result.failed).toEqual([]);
|
|
192
|
+
expect(result.agentDeleted).toBe(true);
|
|
193
|
+
expect(result.completed).toContain("anthropic-identity");
|
|
194
|
+
expect(h.prisma.deleted).toBe(true);
|
|
195
|
+
|
|
196
|
+
const start = h.events.indexOf("anthropic.archiveFederationRule");
|
|
197
|
+
expect(h.events.slice(start)).toEqual([
|
|
198
|
+
"anthropic.archiveFederationRule",
|
|
199
|
+
"store.set(anthropicFederationRuleId)",
|
|
200
|
+
"anthropic.archiveServiceAccount",
|
|
201
|
+
"store.set(anthropicServiceAccountId)",
|
|
202
|
+
"k8s.deleteServiceAccount",
|
|
203
|
+
"store.set(k8sServiceAccountName)",
|
|
204
|
+
]);
|
|
205
|
+
// The workload is torn down before its identity is pulled.
|
|
206
|
+
expect(h.events.indexOf("k8s.deleteDeployment")).toBeLessThan(start);
|
|
207
|
+
|
|
208
|
+
expect(h.anthropic.liveRules()).toHaveLength(0);
|
|
209
|
+
expect(h.anthropic.liveServiceAccounts()).toHaveLength(0);
|
|
210
|
+
expect(
|
|
211
|
+
await exists(h.recorded.getServiceAccount(NAMESPACE, RESOURCE)),
|
|
212
|
+
).toBe(false);
|
|
213
|
+
expect(identityOf(h).anthropicFederationRuleId).toBeNull();
|
|
214
|
+
expect(identityOf(h).anthropicServiceAccountId).toBeNull();
|
|
215
|
+
expect(identityOf(h).k8sServiceAccountName).toBeNull();
|
|
216
|
+
expect(result.manualStepsRequired).toEqual([]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("keeps the row and lists the remaining pieces when the service account archive fails, then a retry finishes", async () => {
|
|
220
|
+
h.anthropic.failNext("archiveServiceAccount");
|
|
221
|
+
|
|
222
|
+
const first = await deleteAgentFully(AGENT_ID, h.deps);
|
|
223
|
+
|
|
224
|
+
expect(first.agentDeleted).toBe(false);
|
|
225
|
+
expect(h.prisma.deleted).toBe(false);
|
|
226
|
+
expect(first.failed.map((f) => f.step)).toEqual(["anthropic-identity"]);
|
|
227
|
+
// The rule was archived and its ID cleared before the failure.
|
|
228
|
+
expect(h.anthropic.liveRules()).toHaveLength(0);
|
|
229
|
+
expect(identityOf(h).anthropicFederationRuleId).toBeNull();
|
|
230
|
+
expect(identityOf(h).anthropicServiceAccountId).not.toBeNull();
|
|
231
|
+
expect(identityOf(h).k8sServiceAccountName).toBe(RESOURCE);
|
|
232
|
+
// The K8s ServiceAccount is untouched: it is removed last.
|
|
233
|
+
expect(
|
|
234
|
+
await exists(h.recorded.getServiceAccount(NAMESPACE, RESOURCE)),
|
|
235
|
+
).toBe(true);
|
|
236
|
+
expect(first.manualStepsRequired.map((s) => s.key)).toEqual([
|
|
237
|
+
"anthropicServiceAccountId",
|
|
238
|
+
"k8sServiceAccountName",
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
const retry = await deleteAgentFully(AGENT_ID, h.deps);
|
|
242
|
+
|
|
243
|
+
expect(retry.failed).toEqual([]);
|
|
244
|
+
expect(retry.agentDeleted).toBe(true);
|
|
245
|
+
expect(h.prisma.deleted).toBe(true);
|
|
246
|
+
expect(h.anthropic.liveServiceAccounts()).toHaveLength(0);
|
|
247
|
+
// The retry only redid what was left: the rule was archived exactly once.
|
|
248
|
+
expect(h.anthropic.callsTo("archiveFederationRule")).toHaveLength(1);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("never archives the service account while the rule is still live", async () => {
|
|
252
|
+
h.anthropic.failNext("archiveFederationRule");
|
|
253
|
+
|
|
254
|
+
const result = await deleteAgentFully(AGENT_ID, h.deps);
|
|
255
|
+
|
|
256
|
+
expect(result.agentDeleted).toBe(false);
|
|
257
|
+
expect(h.anthropic.callsTo("archiveServiceAccount")).toEqual([]);
|
|
258
|
+
expect(h.anthropic.liveRules()).toHaveLength(1);
|
|
259
|
+
expect(result.manualStepsRequired.map((s) => s.key)).toEqual([
|
|
260
|
+
"anthropicFederationRuleId",
|
|
261
|
+
"anthropicServiceAccountId",
|
|
262
|
+
"k8sServiceAccountName",
|
|
263
|
+
]);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("keeps the row when the K8s ServiceAccount delete fails and lists only it", async () => {
|
|
267
|
+
h.k8s.failNext("deleteServiceAccount");
|
|
268
|
+
|
|
269
|
+
const result = await deleteAgentFully(AGENT_ID, h.deps);
|
|
270
|
+
|
|
271
|
+
expect(result.agentDeleted).toBe(false);
|
|
272
|
+
expect(result.manualStepsRequired.map((s) => s.key)).toEqual([
|
|
273
|
+
"k8sServiceAccountName",
|
|
274
|
+
]);
|
|
275
|
+
expect(identityOf(h).anthropicServiceAccountId).toBeNull();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("treats an already-removed piece as done", async () => {
|
|
279
|
+
await h.recorded.deleteServiceAccount(NAMESPACE, RESOURCE);
|
|
280
|
+
const ruleId = identityOf(h).anthropicFederationRuleId as string;
|
|
281
|
+
await h.anthropic.archiveFederationRule(ruleId);
|
|
282
|
+
h.anthropic.failNext(
|
|
283
|
+
"archiveFederationRule",
|
|
284
|
+
new AnthropicApiError("archiveFederationRule", 404),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const result = await deleteAgentFully(AGENT_ID, h.deps);
|
|
288
|
+
|
|
289
|
+
expect(ruleId).toBeTruthy();
|
|
290
|
+
expect(result.failed).toEqual([]);
|
|
291
|
+
expect(result.agentDeleted).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe("deleteAgentFully — provisioner without per-agent identity", () => {
|
|
296
|
+
it("fails the step and checklists the saved pieces rather than leaking them", async () => {
|
|
297
|
+
const h = harness({ perAgent: false });
|
|
298
|
+
h.store.addAgent(AGENT_ID, {
|
|
299
|
+
ownerEmail: OWNER,
|
|
300
|
+
anthropicServiceAccountId: "svac_1",
|
|
301
|
+
anthropicFederationRuleId: "fdrl_1",
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const result = await deleteAgentFully(AGENT_ID, h.deps);
|
|
305
|
+
|
|
306
|
+
expect(result.agentDeleted).toBe(false);
|
|
307
|
+
expect(result.failed.map((f) => f.step)).toEqual(["anthropic-identity"]);
|
|
308
|
+
expect(result.manualStepsRequired.map((s) => s.key)).toEqual([
|
|
309
|
+
"anthropicFederationRuleId",
|
|
310
|
+
"anthropicServiceAccountId",
|
|
311
|
+
]);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("behaves exactly as before for an agent with no identity", async () => {
|
|
315
|
+
const h = harness({ perAgent: false });
|
|
316
|
+
h.store.addAgent(AGENT_ID);
|
|
317
|
+
|
|
318
|
+
const result = await deleteAgentFully(AGENT_ID, h.deps);
|
|
319
|
+
|
|
320
|
+
expect(result.agentDeleted).toBe(true);
|
|
321
|
+
expect(result.completed).not.toContain("anthropic-identity");
|
|
322
|
+
});
|
|
323
|
+
});
|