@cat-factory/app 0.115.3 → 0.116.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/panels/inspector/ServiceTestConfig.vue +153 -1
- package/app/composables/api/environments.ts +16 -1
- package/app/composables/usePipelineErrorToast.ts +4 -0
- package/app/composables/useWorkspaceStream.ts +6 -0
- package/app/stores/environmentTest.spec.ts +103 -0
- package/app/stores/environmentTest.ts +114 -0
- package/app/stores/workspace.spec.ts +1 -0
- package/app/stores/workspace.ts +2 -0
- package/app/types/domain.ts +3 -0
- package/i18n/locales/de.json +22 -1
- package/i18n/locales/en.json +22 -1
- package/i18n/locales/es.json +22 -1
- package/i18n/locales/fr.json +22 -1
- package/i18n/locales/he.json +22 -1
- package/i18n/locales/it.json +22 -1
- package/i18n/locales/ja.json +22 -1
- package/i18n/locales/pl.json +22 -1
- package/i18n/locales/tr.json +22 -1
- package/i18n/locales/uk.json +22 -1
- package/package.json +2 -2
|
@@ -3,11 +3,13 @@ import { computed, onMounted, ref, watch } from 'vue'
|
|
|
3
3
|
import type {
|
|
4
4
|
Block,
|
|
5
5
|
CloudProvider,
|
|
6
|
+
EnvironmentTestStage,
|
|
6
7
|
InstanceSize,
|
|
7
8
|
ProvisionType,
|
|
8
9
|
ServiceProvisioning,
|
|
9
10
|
} from '~/types/domain'
|
|
10
11
|
import type {
|
|
12
|
+
ConflictReason,
|
|
11
13
|
KubernetesManifestSource,
|
|
12
14
|
KubernetesRenderer,
|
|
13
15
|
ProvisioningComposeServiceCandidate,
|
|
@@ -19,6 +21,7 @@ import type {
|
|
|
19
21
|
import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
|
|
20
22
|
import InspectorSection from '~/components/panels/inspector/InspectorSection.vue'
|
|
21
23
|
import { apiErrorEnvelope } from '~/composables/api/errors'
|
|
24
|
+
import { parseConflict } from '~/composables/usePipelineErrorToast'
|
|
22
25
|
|
|
23
26
|
// Service-level (frame) configuration: the service-owned PROVISIONING — the provision
|
|
24
27
|
// TYPE this service produces (`infraless` / `docker-compose` / `kubernetes` / `custom`)
|
|
@@ -44,7 +47,7 @@ const services = useServicesStore()
|
|
|
44
47
|
const infra = useInfraConfigStore()
|
|
45
48
|
const agentRuns = useAgentRunsStore()
|
|
46
49
|
const ui = useUiStore()
|
|
47
|
-
const { t } = useI18n()
|
|
50
|
+
const { t, te } = useI18n()
|
|
48
51
|
|
|
49
52
|
// The custom-manifest-type catalog feeds the `custom` picker. Cheap + shared (coalesced).
|
|
50
53
|
// The repo list backs the detect-from-repo affordance (owner/name lookup).
|
|
@@ -247,6 +250,80 @@ async function generateOrFixManifest() {
|
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
252
|
|
|
253
|
+
// Ephemeral-environment self-test: run the whole create-branch → provision → tear-down →
|
|
254
|
+
// delete-branch cycle against this service's provisioning config and report success / the stage
|
|
255
|
+
// it failed at. The returned run is tracked live (by frame id) via the workspace stream store.
|
|
256
|
+
const envTest = useEnvironmentTestStore()
|
|
257
|
+
const envTestStarting = ref(false)
|
|
258
|
+
const envTestError = ref<string | null>(null)
|
|
259
|
+
// The newest self-test run for this frame — re-attaches after a reconnect (the run is carried in
|
|
260
|
+
// the snapshot while running), so the live stage keeps showing without a locally-held id.
|
|
261
|
+
const envTestRun = computed(() => envTest.runForBlock(props.block.id))
|
|
262
|
+
const envTestRunning = computed(() => envTestRun.value?.status === 'running')
|
|
263
|
+
// Nothing to provision for an `infraless` service, so there is nothing to test.
|
|
264
|
+
const canTestEnv = computed(() => provisionType.value !== 'infraless')
|
|
265
|
+
|
|
266
|
+
// Per-stage label KEYS, exhaustive over the contracts `EnvironmentTestStage` union: a new
|
|
267
|
+
// backend stage fails THIS typecheck until mapped (the key is resolved at runtime, so the
|
|
268
|
+
// typed-message-keys check can't see the `t()` lookup — the map's exhaustiveness is the
|
|
269
|
+
// drift guard, same pattern as `CONFLICT_TITLE_KEYS`).
|
|
270
|
+
const ENV_TEST_STAGE_KEYS: Record<EnvironmentTestStage, string> = {
|
|
271
|
+
creating_branch: 'inspector.testConfig.envTest.stage.creating_branch',
|
|
272
|
+
provisioning: 'inspector.testConfig.envTest.stage.provisioning',
|
|
273
|
+
tearing_down: 'inspector.testConfig.envTest.stage.tearing_down',
|
|
274
|
+
deleting_branch: 'inspector.testConfig.envTest.stage.deleting_branch',
|
|
275
|
+
done: 'inspector.testConfig.envTest.stage.done',
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function envTestStageLabel(stage: EnvironmentTestStage): string {
|
|
279
|
+
const key = ENV_TEST_STAGE_KEYS[stage]
|
|
280
|
+
// `te`-guarded so a locale missing the key shows the raw stage id, never a raw message key.
|
|
281
|
+
return te(key) ? t(key) : stage
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// The start preflight's machine-readable 409 reasons, mapped to their localized titles —
|
|
285
|
+
// exhaustive over the contracts `env_test_*` conflict reasons (same drift guard as above).
|
|
286
|
+
// The raw backend `message` is only the last-resort fallback for unmapped/non-conflict errors.
|
|
287
|
+
const ENV_TEST_CONFLICT_KEYS: Record<Extract<ConflictReason, `env_test_${string}`>, string> = {
|
|
288
|
+
env_test_not_a_frame: 'errors.conflict.title.env_test_not_a_frame',
|
|
289
|
+
env_test_infraless: 'errors.conflict.title.env_test_infraless',
|
|
290
|
+
env_test_not_provisionable: 'errors.conflict.title.env_test_not_provisionable',
|
|
291
|
+
env_test_no_vcs: 'errors.conflict.title.env_test_no_vcs',
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function envTestErrorText(e: unknown): string {
|
|
295
|
+
const reason = parseConflict(e)?.reason
|
|
296
|
+
const key =
|
|
297
|
+
reason && reason in ENV_TEST_CONFLICT_KEYS
|
|
298
|
+
? ENV_TEST_CONFLICT_KEYS[reason as keyof typeof ENV_TEST_CONFLICT_KEYS]
|
|
299
|
+
: undefined
|
|
300
|
+
if (key && te(key)) return t(key)
|
|
301
|
+
return apiErrorEnvelope(e)?.message ?? (e instanceof Error ? e.message : String(e))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function startEnvTest() {
|
|
305
|
+
if (!canTestEnv.value || envTestStarting.value || envTestRunning.value) return
|
|
306
|
+
envTestStarting.value = true
|
|
307
|
+
envTestError.value = null
|
|
308
|
+
try {
|
|
309
|
+
await envTest.start(props.block.id)
|
|
310
|
+
} catch (e) {
|
|
311
|
+
envTestError.value = envTestErrorText(e)
|
|
312
|
+
} finally {
|
|
313
|
+
envTestStarting.value = false
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function stopEnvTest() {
|
|
318
|
+
const run = envTestRun.value
|
|
319
|
+
if (!run || run.status !== 'running') return
|
|
320
|
+
try {
|
|
321
|
+
await envTest.stop(run.id)
|
|
322
|
+
} catch (e) {
|
|
323
|
+
envTestError.value = envTestErrorText(e)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
250
327
|
// The provisioning hints (cloud provider + instance size) are advisory inputs to the
|
|
251
328
|
// ephemeral-environment provisioner, not commonly tuned — keep them collapsed by default.
|
|
252
329
|
const showProvisioning = ref(false)
|
|
@@ -923,5 +1000,80 @@ function setSize(value: InstanceSize) {
|
|
|
923
1000
|
</div>
|
|
924
1001
|
</div>
|
|
925
1002
|
</InspectorSection>
|
|
1003
|
+
|
|
1004
|
+
<!-- Ephemeral-environment self-test: exercise the whole provisioning lifecycle against a
|
|
1005
|
+
throwaway branch and report success / the failing stage. Disabled for `infraless`. -->
|
|
1006
|
+
<div class="mt-3 space-y-2 border-t border-white/5 pt-3" data-testid="env-test-section">
|
|
1007
|
+
<div class="flex items-center justify-between gap-2">
|
|
1008
|
+
<div class="min-w-0">
|
|
1009
|
+
<p class="text-[11px] font-medium text-slate-300">
|
|
1010
|
+
{{ t('inspector.testConfig.envTest.title') }}
|
|
1011
|
+
</p>
|
|
1012
|
+
<p class="text-[11px] text-slate-400">{{ t('inspector.testConfig.envTest.hint') }}</p>
|
|
1013
|
+
</div>
|
|
1014
|
+
<UButton
|
|
1015
|
+
v-if="!envTestRunning"
|
|
1016
|
+
icon="i-lucide-flask-conical"
|
|
1017
|
+
size="xs"
|
|
1018
|
+
color="primary"
|
|
1019
|
+
variant="soft"
|
|
1020
|
+
data-testid="env-test-start"
|
|
1021
|
+
:loading="envTestStarting"
|
|
1022
|
+
:disabled="!canTestEnv"
|
|
1023
|
+
@click="startEnvTest"
|
|
1024
|
+
>
|
|
1025
|
+
{{ t('inspector.testConfig.envTest.start') }}
|
|
1026
|
+
</UButton>
|
|
1027
|
+
<UButton
|
|
1028
|
+
v-else
|
|
1029
|
+
icon="i-lucide-square"
|
|
1030
|
+
size="xs"
|
|
1031
|
+
color="neutral"
|
|
1032
|
+
variant="ghost"
|
|
1033
|
+
data-testid="env-test-stop"
|
|
1034
|
+
@click="stopEnvTest"
|
|
1035
|
+
>
|
|
1036
|
+
{{ t('inspector.testConfig.envTest.stop') }}
|
|
1037
|
+
</UButton>
|
|
1038
|
+
</div>
|
|
1039
|
+
|
|
1040
|
+
<p v-if="!canTestEnv" class="text-[11px] text-slate-500">
|
|
1041
|
+
{{ t('inspector.testConfig.envTest.infraless') }}
|
|
1042
|
+
</p>
|
|
1043
|
+
|
|
1044
|
+
<!-- Live stage + terminal outcome of the tracked run (pushed via the workspace stream). -->
|
|
1045
|
+
<p
|
|
1046
|
+
v-if="envTestRun"
|
|
1047
|
+
class="text-[11px]"
|
|
1048
|
+
:class="{
|
|
1049
|
+
'text-sky-300/80': envTestRun.status === 'running',
|
|
1050
|
+
'text-emerald-300/80': envTestRun.status === 'succeeded',
|
|
1051
|
+
'text-rose-300/80': envTestRun.status === 'failed',
|
|
1052
|
+
}"
|
|
1053
|
+
data-testid="env-test-status"
|
|
1054
|
+
>
|
|
1055
|
+
<template v-if="envTestRun.status === 'running'">
|
|
1056
|
+
{{
|
|
1057
|
+
t('inspector.testConfig.envTest.running', {
|
|
1058
|
+
stage: envTestStageLabel(envTestRun.stage),
|
|
1059
|
+
})
|
|
1060
|
+
}}
|
|
1061
|
+
</template>
|
|
1062
|
+
<template v-else-if="envTestRun.status === 'succeeded'">
|
|
1063
|
+
{{ t('inspector.testConfig.envTest.succeeded') }}
|
|
1064
|
+
</template>
|
|
1065
|
+
<template v-else>
|
|
1066
|
+
{{ t('inspector.testConfig.envTest.failed') }}
|
|
1067
|
+
<template v-if="envTestRun.failedStage">
|
|
1068
|
+
({{ envTestStageLabel(envTestRun.failedStage) }})
|
|
1069
|
+
</template>
|
|
1070
|
+
<span v-if="envTestRun.error" class="block text-rose-300/70">{{ envTestRun.error }}</span>
|
|
1071
|
+
</template>
|
|
1072
|
+
</p>
|
|
1073
|
+
|
|
1074
|
+
<p v-if="envTestError" class="text-[11px] text-rose-400" data-testid="env-test-error">
|
|
1075
|
+
{{ envTestError }}
|
|
1076
|
+
</p>
|
|
1077
|
+
</div>
|
|
926
1078
|
</InspectorSection>
|
|
927
1079
|
</template>
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getEnvironmentTestContract,
|
|
3
|
+
listEnvironmentsContract,
|
|
4
|
+
provisionEnvironmentContract,
|
|
5
|
+
startEnvironmentTestContract,
|
|
6
|
+
stopEnvironmentTestContract,
|
|
7
|
+
} from '@cat-factory/contracts'
|
|
2
8
|
import type { ProvisionEnvironmentInput } from '@cat-factory/contracts'
|
|
3
9
|
import type { ApiContext } from './context'
|
|
4
10
|
|
|
@@ -12,5 +18,14 @@ export function environmentsApi({ send, ws }: ApiContext) {
|
|
|
12
18
|
// wizard's "trial provision" against the just-saved config. Returns the resulting handle.
|
|
13
19
|
provisionEnvironment: (workspaceId: string, body: ProvisionEnvironmentInput) =>
|
|
14
20
|
send(provisionEnvironmentContract, { pathPrefix: ws(workspaceId), body }),
|
|
21
|
+
|
|
22
|
+
// Ephemeral-environment self-test: start a full create-branch → provision → tear-down →
|
|
23
|
+
// delete-branch cycle against a service frame, then read / stop its run.
|
|
24
|
+
startEnvironmentTest: (workspaceId: string, blockId: string) =>
|
|
25
|
+
send(startEnvironmentTestContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
|
|
26
|
+
getEnvironmentTest: (workspaceId: string, id: string) =>
|
|
27
|
+
send(getEnvironmentTestContract, { pathPrefix: ws(workspaceId), pathParams: { id } }),
|
|
28
|
+
stopEnvironmentTest: (workspaceId: string, id: string) =>
|
|
29
|
+
send(stopEnvironmentTestContract, { pathPrefix: ws(workspaceId), pathParams: { id } }),
|
|
15
30
|
}
|
|
16
31
|
}
|
|
@@ -55,6 +55,10 @@ const CONFLICT_TITLE_KEYS: Record<Exclude<ConflictReason, BespokeConflictReason>
|
|
|
55
55
|
model_policy_blocked: 'errors.conflict.title.model_policy_blocked',
|
|
56
56
|
model_policy_unsupported: 'errors.conflict.title.model_policy_unsupported',
|
|
57
57
|
deployer_required_before_tester: 'errors.conflict.title.deployer_required_before_tester',
|
|
58
|
+
env_test_not_a_frame: 'errors.conflict.title.env_test_not_a_frame',
|
|
59
|
+
env_test_infraless: 'errors.conflict.title.env_test_infraless',
|
|
60
|
+
env_test_not_provisionable: 'errors.conflict.title.env_test_not_provisionable',
|
|
61
|
+
env_test_no_vcs: 'errors.conflict.title.env_test_no_vcs',
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
/**
|
|
@@ -18,6 +18,7 @@ export function useWorkspaceStream() {
|
|
|
18
18
|
const execution = useExecutionStore()
|
|
19
19
|
const board = useBoardStore()
|
|
20
20
|
const agentRuns = useAgentRunsStore()
|
|
21
|
+
const environmentTest = useEnvironmentTestStore()
|
|
21
22
|
const notifications = useNotificationsStore()
|
|
22
23
|
const observability = useObservabilityStore()
|
|
23
24
|
const requirements = useRequirementsStore()
|
|
@@ -102,6 +103,11 @@ export function useWorkspaceStream() {
|
|
|
102
103
|
// the infrastructure-providers window's "repairing…" indicator updates in place
|
|
103
104
|
// (then flips to ok / residual issues / a failure) without a refetch. No board block.
|
|
104
105
|
agentRuns.upsertEnvConfigRepair(event.job)
|
|
106
|
+
} else if (event.type === 'envTest') {
|
|
107
|
+
// An ephemeral-environment self-test advanced a stage — patch the run so the service
|
|
108
|
+
// inspector's "Test environment creation" control shows the live stage + final
|
|
109
|
+
// outcome in place without a refetch. No board block.
|
|
110
|
+
environmentTest.upsert(event.run)
|
|
105
111
|
} else if (event.type === 'notification') {
|
|
106
112
|
// A PR needs a merge decision, a pipeline finished, or CI gave up — patch the
|
|
107
113
|
// inbox + per-block badge in place (resolved ones drop out of the inbox).
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
2
|
+
import type { EnvironmentTestRun } from '~/types/domain'
|
|
3
|
+
import { useEnvironmentTestStore } from '~/stores/environmentTest'
|
|
4
|
+
|
|
5
|
+
// The store resolves `useApi()` at setup; override the inert global stub from
|
|
6
|
+
// `test/setup.ts` with a per-suite mock so the hydrate reconcile point-read is observable.
|
|
7
|
+
const apiMock = { getEnvironmentTest: vi.fn() }
|
|
8
|
+
vi.stubGlobal('useApi', () => apiMock)
|
|
9
|
+
|
|
10
|
+
/** Minimal EnvironmentTestRun factory — only the fields the store's reconcile logic touches. */
|
|
11
|
+
function run(id: string, over: Partial<EnvironmentTestRun> = {}): EnvironmentTestRun {
|
|
12
|
+
return {
|
|
13
|
+
id,
|
|
14
|
+
workspaceId: 'ws_test',
|
|
15
|
+
blockId: `blk_${id}`,
|
|
16
|
+
status: 'running',
|
|
17
|
+
stage: 'provisioning',
|
|
18
|
+
branch: null,
|
|
19
|
+
envUrl: null,
|
|
20
|
+
error: null,
|
|
21
|
+
failedStage: null,
|
|
22
|
+
createdAt: 1,
|
|
23
|
+
updatedAt: 1,
|
|
24
|
+
...over,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('environmentTest store — monotonic run reconcile', () => {
|
|
29
|
+
let store: ReturnType<typeof useEnvironmentTestStore>
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
apiMock.getEnvironmentTest = vi.fn(async () => {
|
|
32
|
+
throw new Error('not stubbed')
|
|
33
|
+
})
|
|
34
|
+
store = useEnvironmentTestStore()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('hydrate does NOT regress a run a newer live event already advanced', () => {
|
|
38
|
+
// A live `envTest: failed` event landed first (newer updatedAt).
|
|
39
|
+
store.upsert(run('r1', { status: 'failed', failedStage: 'provisioning', updatedAt: 5 }))
|
|
40
|
+
// A lagging `workspace.refresh()` then hydrates a STALE snapshot that still saw the run
|
|
41
|
+
// as `running` (older updatedAt) — it must NOT clobber the terminal state (terminal runs
|
|
42
|
+
// emit nothing further, so the inspector would be stuck on "testing" forever).
|
|
43
|
+
store.hydrate([run('r1', { status: 'running', updatedAt: 2 })], 'ws_test')
|
|
44
|
+
expect(store.runForBlock('blk_r1')!.status).toBe('failed')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('hydrate does NOT drop a live-added run the stale snapshot never saw', () => {
|
|
48
|
+
// Terminal runs are omitted from the snapshot BY DESIGN, so a just-finished run the
|
|
49
|
+
// inspector still shows must survive a full refresh.
|
|
50
|
+
store.upsert(run('r1', { status: 'succeeded', stage: 'done', updatedAt: 5 }))
|
|
51
|
+
store.hydrate([], 'ws_test')
|
|
52
|
+
expect(store.runForBlock('blk_r1')!.status).toBe('succeeded')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('hydrate point-reads a preserved RUNNING run the snapshot omitted (finished offline)', async () => {
|
|
56
|
+
// The run was still `running` when the socket dropped; it finished while disconnected, so
|
|
57
|
+
// the reconnect snapshot no longer carries it and no event replays — the hydrate must
|
|
58
|
+
// re-read it to pick up the outcome instead of stranding a stale "testing" state.
|
|
59
|
+
store.upsert(run('r1', { status: 'running', updatedAt: 5 }))
|
|
60
|
+
apiMock.getEnvironmentTest = vi.fn(async () =>
|
|
61
|
+
run('r1', { status: 'succeeded', stage: 'done', updatedAt: 9 }),
|
|
62
|
+
)
|
|
63
|
+
store.hydrate([], 'ws_test')
|
|
64
|
+
expect(apiMock.getEnvironmentTest).toHaveBeenCalledWith('ws_test', 'r1')
|
|
65
|
+
await vi.waitFor(() => expect(store.runForBlock('blk_r1')!.status).toBe('succeeded'))
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('a STALE point-read cannot regress a run a live event advanced meanwhile', async () => {
|
|
69
|
+
store.upsert(run('r1', { status: 'running', updatedAt: 5 }))
|
|
70
|
+
// The reconcile read resolves with an OLDER view of the run than the live event that
|
|
71
|
+
// lands while it is in flight — the monotonic upsert must keep the newer state.
|
|
72
|
+
apiMock.getEnvironmentTest = vi.fn(async () => run('r1', { status: 'running', updatedAt: 4 }))
|
|
73
|
+
store.hydrate([], 'ws_test')
|
|
74
|
+
store.upsert(run('r1', { status: 'failed', failedStage: 'tearing_down', updatedAt: 8 }))
|
|
75
|
+
await vi.waitFor(() => expect(apiMock.getEnvironmentTest).toHaveBeenCalled())
|
|
76
|
+
await Promise.resolve()
|
|
77
|
+
expect(store.runForBlock('blk_r1')!.status).toBe('failed')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('hydrate DROPS a cached run from a different workspace (board switch starts clean)', () => {
|
|
81
|
+
store.upsert(run('r1', { status: 'failed', updatedAt: 5, workspaceId: 'ws_other' }))
|
|
82
|
+
store.hydrate([run('r2', { workspaceId: 'ws_test' })], 'ws_test')
|
|
83
|
+
expect(store.runs.map((r) => r.id)).toEqual(['r2'])
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('hydrate DOES apply a genuinely newer snapshot', () => {
|
|
87
|
+
store.upsert(run('r1', { status: 'running', updatedAt: 2 }))
|
|
88
|
+
store.hydrate(
|
|
89
|
+
[run('r1', { status: 'running', stage: 'tearing_down', updatedAt: 9 })],
|
|
90
|
+
'ws_test',
|
|
91
|
+
)
|
|
92
|
+
expect(store.runForBlock('blk_r1')!.stage).toBe('tearing_down')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('upsert ignores an older/out-of-order write but applies newer/equal', () => {
|
|
96
|
+
store.upsert(run('r1', { status: 'failed', updatedAt: 5 }))
|
|
97
|
+
// e.g. a `start()` response resolving AFTER the fast-failing run's terminal event landed.
|
|
98
|
+
store.upsert(run('r1', { status: 'running', stage: 'creating_branch', updatedAt: 3 }))
|
|
99
|
+
expect(store.runForBlock('blk_r1')!.status).toBe('failed')
|
|
100
|
+
store.upsert(run('r1', { status: 'succeeded', stage: 'done', updatedAt: 5 }))
|
|
101
|
+
expect(store.runForBlock('blk_r1')!.status).toBe('succeeded')
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import type { EnvironmentTestRun } from '~/types/domain'
|
|
4
|
+
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Ephemeral-environment self-test runs. A developer starts one from a service frame's inspector
|
|
8
|
+
* (`POST …/blocks/:id/environment-test`); the backend drives the create-branch → provision →
|
|
9
|
+
* tear-down → delete-branch cycle durably and pushes live `envTest` stage events, which
|
|
10
|
+
* `useWorkspaceStream` folds in via {@link upsert}. In-flight runs also arrive in the workspace
|
|
11
|
+
* snapshot ({@link hydrate}) so the inspector re-attaches to a running test after a reconnect.
|
|
12
|
+
*
|
|
13
|
+
* Runs are keyed by their FRAME block id for the inspector's per-service lookup ({@link runForBlock}
|
|
14
|
+
* returns the newest run for a block). Terminal runs are kept in memory for the session so the
|
|
15
|
+
* inspector can show the last outcome; the snapshot only carries running ones.
|
|
16
|
+
*/
|
|
17
|
+
export const useEnvironmentTestStore = defineStore('environmentTest', () => {
|
|
18
|
+
const api = useApi()
|
|
19
|
+
|
|
20
|
+
/** All known runs (running + this session's terminal ones), newest first. */
|
|
21
|
+
const runs = ref<EnvironmentTestRun[]>([])
|
|
22
|
+
|
|
23
|
+
function sortByCreated(list: EnvironmentTestRun[]): EnvironmentTestRun[] {
|
|
24
|
+
return [...list].sort((a, b) => b.createdAt - a.createdAt)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Reconcile the cached runs with a server snapshot for `workspaceId`. A snapshot is
|
|
29
|
+
* authoritative EXCEPT where a live `envTest` event has already advanced (or ADDED) a run
|
|
30
|
+
* past what this (possibly stale) read observed — a `board`-event refresh or the on-connect
|
|
31
|
+
* resync can resolve AFTER a newer event already landed. Same two clobber hazards as
|
|
32
|
+
* `agentRuns.hydrate`, both handled here:
|
|
33
|
+
* - REGRESS: a run present in BOTH the snapshot and the cache — keep the newer-by-`updatedAt`
|
|
34
|
+
* version, so a lagging refresh can't revert a `failed`/`succeeded` run to `running`
|
|
35
|
+
* (terminal runs emit nothing further, so the inspector would be stuck on "testing").
|
|
36
|
+
* - DROP: a run a live event just ADDED that the (older) snapshot never saw — replacing from
|
|
37
|
+
* the snapshot alone would silently drop it (and terminal runs are omitted from the
|
|
38
|
+
* snapshot by design, so a finished run the inspector still shows would vanish).
|
|
39
|
+
* Preserve such cached runs, scoped to `workspaceId` so a board SWITCH still starts clean.
|
|
40
|
+
*
|
|
41
|
+
* A preserved RUNNING run absent from the snapshot may also have reached terminal while the
|
|
42
|
+
* socket was down (no event replays, and the snapshot omits terminal runs) — point-read it
|
|
43
|
+
* best-effort to pick up the outcome; {@link upsert}'s monotonic guard makes the read safe
|
|
44
|
+
* against racing live events.
|
|
45
|
+
*/
|
|
46
|
+
function hydrate(snapshotRuns: EnvironmentTestRun[], workspaceId: string) {
|
|
47
|
+
const incomingIds = new Set(snapshotRuns.map((r) => r.id))
|
|
48
|
+
const held = new Map(runs.value.map((r) => [r.id, r]))
|
|
49
|
+
const reconciled = snapshotRuns.map((incoming) => {
|
|
50
|
+
const current = held.get(incoming.id)
|
|
51
|
+
return current && current.updatedAt > incoming.updatedAt ? current : incoming
|
|
52
|
+
})
|
|
53
|
+
const preserved = [...held.values()].filter(
|
|
54
|
+
(r) => !incomingIds.has(r.id) && r.workspaceId === workspaceId,
|
|
55
|
+
)
|
|
56
|
+
runs.value = sortByCreated([...reconciled, ...preserved])
|
|
57
|
+
// A still-`running` preserved run wasn't in the snapshot, so either the snapshot is stale
|
|
58
|
+
// (the run is genuinely newer) or the run FINISHED while we were disconnected — resolve
|
|
59
|
+
// which by re-reading it (non-blocking; failures leave the cached state as-is).
|
|
60
|
+
for (const r of preserved) {
|
|
61
|
+
if (r.status === 'running') void reconcileRun(workspaceId, r.id)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Best-effort point-read of one run, folded in through the monotonic {@link upsert}. */
|
|
66
|
+
async function reconcileRun(workspaceId: string, id: string) {
|
|
67
|
+
try {
|
|
68
|
+
upsert(await api.getEnvironmentTest(workspaceId, id))
|
|
69
|
+
} catch {
|
|
70
|
+
// Best-effort: a transient fetch failure just leaves the cached state; the next
|
|
71
|
+
// snapshot/event reconciles it.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fold a live-pushed (or freshly-started/stopped) run into the cache. Monotonic by
|
|
77
|
+
* `updatedAt`: never let a stale/out-of-order write regress a run a newer one already
|
|
78
|
+
* advanced — e.g. a `start()` response resolving AFTER a fast-failing run's terminal
|
|
79
|
+
* event already landed (same guard as {@link hydrate}).
|
|
80
|
+
*/
|
|
81
|
+
function upsert(run: EnvironmentTestRun) {
|
|
82
|
+
const i = runs.value.findIndex((r) => r.id === run.id)
|
|
83
|
+
if (i >= 0) {
|
|
84
|
+
if (run.updatedAt >= runs.value[i]!.updatedAt) runs.value[i] = run
|
|
85
|
+
} else runs.value.unshift(run)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function runById(id: string): EnvironmentTestRun | undefined {
|
|
89
|
+
return runs.value.find((r) => r.id === id)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The newest run for a service frame — the inspector's per-service attach point. */
|
|
93
|
+
function runForBlock(blockId: string): EnvironmentTestRun | undefined {
|
|
94
|
+
return runs.value.find((r) => r.blockId === blockId)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Start a self-test against a service frame; the returned run is tracked immediately. */
|
|
98
|
+
async function start(blockId: string): Promise<EnvironmentTestRun> {
|
|
99
|
+
const ws = useWorkspaceStore()
|
|
100
|
+
const run = await api.startEnvironmentTest(ws.requireId(), blockId)
|
|
101
|
+
upsert(run)
|
|
102
|
+
return run
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Stop a running self-test (best-effort cleanup, then failed). */
|
|
106
|
+
async function stop(id: string): Promise<EnvironmentTestRun> {
|
|
107
|
+
const ws = useWorkspaceStore()
|
|
108
|
+
const run = await api.stopEnvironmentTest(ws.requireId(), id)
|
|
109
|
+
upsert(run)
|
|
110
|
+
return run
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { runs, hydrate, upsert, runById, runForBlock, start, stop }
|
|
114
|
+
})
|
package/app/stores/workspace.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { useBoardStore } from '~/stores/board'
|
|
|
12
12
|
import { usePipelinesStore } from '~/stores/pipelines'
|
|
13
13
|
import { useExecutionStore } from '~/stores/execution'
|
|
14
14
|
import { useAgentRunsStore } from '~/stores/agentRuns'
|
|
15
|
+
import { useEnvironmentTestStore } from '~/stores/environmentTest'
|
|
15
16
|
import { useNotificationsStore } from '~/stores/notifications'
|
|
16
17
|
import { useRiskPoliciesStore } from '~/stores/riskPolicies'
|
|
17
18
|
import { useSharedStacksStore } from '~/stores/sharedStacks'
|
|
@@ -120,6 +121,7 @@ export const useWorkspaceStore = defineStore(
|
|
|
120
121
|
useExecutionStore().hydrate(snapshot.executions, snapshot.workspace.id)
|
|
121
122
|
useAgentRunsStore().hydrate(snapshot.bootstrapJobs ?? [], snapshot.workspace.id)
|
|
122
123
|
useAgentRunsStore().hydrateEnvConfigRepair(snapshot.envConfigRepairJobs ?? [])
|
|
124
|
+
useEnvironmentTestStore().hydrate(snapshot.environmentTestRuns ?? [], snapshot.workspace.id)
|
|
123
125
|
useNotificationsStore().hydrate(snapshot.notifications ?? [])
|
|
124
126
|
useRiskPoliciesStore().hydrate(
|
|
125
127
|
snapshot.riskPolicies ?? [],
|
package/app/types/domain.ts
CHANGED
package/i18n/locales/de.json
CHANGED
|
@@ -1093,6 +1093,23 @@
|
|
|
1093
1093
|
"title": "Einrichtung der Compose-Umgebung",
|
|
1094
1094
|
"hint": "Konfiguriere das Rezept, die Preflights und den Handler, damit der Deployer diesen Stack bereitstellt.",
|
|
1095
1095
|
"open": "Assistent öffnen"
|
|
1096
|
+
},
|
|
1097
|
+
"envTest": {
|
|
1098
|
+
"title": "Umgebungserstellung testen",
|
|
1099
|
+
"hint": "Führt den gesamten Lebenszyklus gegen einen Wegwerf-Branch aus: Branch erstellen, bereitstellen, abbauen, Branch löschen.",
|
|
1100
|
+
"start": "Umgebungserstellung testen",
|
|
1101
|
+
"stop": "Stopp",
|
|
1102
|
+
"infraless": "Konfiguriere oben einen Bereitstellungstyp, um die Umgebungserstellung zu testen.",
|
|
1103
|
+
"running": "Wird getestet: {stage}",
|
|
1104
|
+
"succeeded": "Test bestanden: Die Umgebung wurde erstellt und abgebaut, und der Branch wurde gelöscht.",
|
|
1105
|
+
"failed": "Test fehlgeschlagen",
|
|
1106
|
+
"stage": {
|
|
1107
|
+
"creating_branch": "Branch wird erstellt",
|
|
1108
|
+
"provisioning": "Umgebung wird bereitgestellt",
|
|
1109
|
+
"tearing_down": "Umgebung wird abgebaut",
|
|
1110
|
+
"deleting_branch": "Branch wird gelöscht",
|
|
1111
|
+
"done": "fertig"
|
|
1112
|
+
}
|
|
1096
1113
|
}
|
|
1097
1114
|
},
|
|
1098
1115
|
"agentConfig": {
|
|
@@ -3786,7 +3803,11 @@
|
|
|
3786
3803
|
"visual_pipeline_no_frontend": "Kein Frontend zum Testen",
|
|
3787
3804
|
"model_policy_blocked": "Modell durch Kontorichtlinie blockiert",
|
|
3788
3805
|
"model_policy_unsupported": "Modellrichtlinie hier nicht verfügbar",
|
|
3789
|
-
"deployer_required_before_tester": "Füge einen Deployer vor dem Tester hinzu"
|
|
3806
|
+
"deployer_required_before_tester": "Füge einen Deployer vor dem Tester hinzu",
|
|
3807
|
+
"env_test_not_a_frame": "Kein Dienst",
|
|
3808
|
+
"env_test_infraless": "Nichts zu testen",
|
|
3809
|
+
"env_test_not_provisionable": "Umgebungs-Handler nicht konfiguriert",
|
|
3810
|
+
"env_test_no_vcs": "Git-Anbieter nicht verbunden"
|
|
3790
3811
|
},
|
|
3791
3812
|
"fallbackMessage": "Diese Aktion steht im Konflikt mit dem aktuellen Zustand.",
|
|
3792
3813
|
"providersUnconfigured": {
|
package/i18n/locales/en.json
CHANGED
|
@@ -475,7 +475,11 @@
|
|
|
475
475
|
"visual_pipeline_no_frontend": "No frontend to test",
|
|
476
476
|
"model_policy_blocked": "Model blocked by account policy",
|
|
477
477
|
"model_policy_unsupported": "Model policy not available here",
|
|
478
|
-
"deployer_required_before_tester": "Add a Deployer before the Tester"
|
|
478
|
+
"deployer_required_before_tester": "Add a Deployer before the Tester",
|
|
479
|
+
"env_test_not_a_frame": "Not a service",
|
|
480
|
+
"env_test_infraless": "Nothing to test",
|
|
481
|
+
"env_test_not_provisionable": "Environment handler not configured",
|
|
482
|
+
"env_test_no_vcs": "Git provider not connected"
|
|
479
483
|
},
|
|
480
484
|
"fallbackMessage": "This action conflicts with the current state.",
|
|
481
485
|
"providersUnconfigured": {
|
|
@@ -837,6 +841,23 @@
|
|
|
837
841
|
"title": "Compose environment setup",
|
|
838
842
|
"hint": "Configure the recipe, preflights, and handler so the Deployer provisions this stack.",
|
|
839
843
|
"open": "Open wizard"
|
|
844
|
+
},
|
|
845
|
+
"envTest": {
|
|
846
|
+
"title": "Test environment creation",
|
|
847
|
+
"hint": "Runs the whole lifecycle against a throwaway branch: create branch, provision, tear down, delete branch.",
|
|
848
|
+
"start": "Test environment creation",
|
|
849
|
+
"stop": "Stop",
|
|
850
|
+
"infraless": "Configure a provision type above to test environment creation.",
|
|
851
|
+
"running": "Testing: {stage}",
|
|
852
|
+
"succeeded": "Test passed: the environment was created and torn down, and the branch was deleted.",
|
|
853
|
+
"failed": "Test failed",
|
|
854
|
+
"stage": {
|
|
855
|
+
"creating_branch": "creating branch",
|
|
856
|
+
"provisioning": "provisioning environment",
|
|
857
|
+
"tearing_down": "tearing down environment",
|
|
858
|
+
"deleting_branch": "deleting branch",
|
|
859
|
+
"done": "done"
|
|
860
|
+
}
|
|
840
861
|
}
|
|
841
862
|
},
|
|
842
863
|
"agentConfig": {
|
package/i18n/locales/es.json
CHANGED
|
@@ -436,7 +436,11 @@
|
|
|
436
436
|
"visual_pipeline_no_frontend": "No hay frontend que probar",
|
|
437
437
|
"model_policy_blocked": "Modelo bloqueado por la política de la cuenta",
|
|
438
438
|
"model_policy_unsupported": "La política de modelos no está disponible aquí",
|
|
439
|
-
"deployer_required_before_tester": "Añade un Deployer antes del Tester"
|
|
439
|
+
"deployer_required_before_tester": "Añade un Deployer antes del Tester",
|
|
440
|
+
"env_test_not_a_frame": "No es un servicio",
|
|
441
|
+
"env_test_infraless": "Nada que probar",
|
|
442
|
+
"env_test_not_provisionable": "Gestor de entorno no configurado",
|
|
443
|
+
"env_test_no_vcs": "Proveedor de Git no conectado"
|
|
440
444
|
},
|
|
441
445
|
"fallbackMessage": "Esta acción entra en conflicto con el estado actual.",
|
|
442
446
|
"providersUnconfigured": {
|
|
@@ -783,6 +787,23 @@
|
|
|
783
787
|
"title": "Configuración de entorno Compose",
|
|
784
788
|
"hint": "Configura la receta, las comprobaciones previas y el gestor para que el Deployer aprovisione este stack.",
|
|
785
789
|
"open": "Abrir asistente"
|
|
790
|
+
},
|
|
791
|
+
"envTest": {
|
|
792
|
+
"title": "Probar la creación del entorno",
|
|
793
|
+
"hint": "Ejecuta todo el ciclo de vida sobre una rama desechable: crear rama, aprovisionar, desmontar, eliminar rama.",
|
|
794
|
+
"start": "Probar la creación del entorno",
|
|
795
|
+
"stop": "Detener",
|
|
796
|
+
"infraless": "Configura arriba un tipo de aprovisionamiento para probar la creación del entorno.",
|
|
797
|
+
"running": "Probando: {stage}",
|
|
798
|
+
"succeeded": "Prueba superada: el entorno se creó y se desmontó, y la rama se eliminó.",
|
|
799
|
+
"failed": "La prueba falló",
|
|
800
|
+
"stage": {
|
|
801
|
+
"creating_branch": "creando rama",
|
|
802
|
+
"provisioning": "aprovisionando entorno",
|
|
803
|
+
"tearing_down": "desmontando entorno",
|
|
804
|
+
"deleting_branch": "eliminando rama",
|
|
805
|
+
"done": "listo"
|
|
806
|
+
}
|
|
786
807
|
}
|
|
787
808
|
},
|
|
788
809
|
"agentConfig": {
|
package/i18n/locales/fr.json
CHANGED
|
@@ -436,7 +436,11 @@
|
|
|
436
436
|
"visual_pipeline_no_frontend": "Aucun frontend à tester",
|
|
437
437
|
"model_policy_blocked": "Modèle bloqué par la politique du compte",
|
|
438
438
|
"model_policy_unsupported": "La politique de modèles n'est pas disponible ici",
|
|
439
|
-
"deployer_required_before_tester": "Ajoutez un Deployer avant le Testeur"
|
|
439
|
+
"deployer_required_before_tester": "Ajoutez un Deployer avant le Testeur",
|
|
440
|
+
"env_test_not_a_frame": "Pas un service",
|
|
441
|
+
"env_test_infraless": "Rien à tester",
|
|
442
|
+
"env_test_not_provisionable": "Gestionnaire d'environnement non configuré",
|
|
443
|
+
"env_test_no_vcs": "Fournisseur Git non connecté"
|
|
440
444
|
},
|
|
441
445
|
"fallbackMessage": "Cette action est en conflit avec l’état actuel.",
|
|
442
446
|
"providersUnconfigured": {
|
|
@@ -783,6 +787,23 @@
|
|
|
783
787
|
"title": "Configuration d'environnement Compose",
|
|
784
788
|
"hint": "Configurez la recette, les vérifications préalables et le gestionnaire pour que le Deployer provisionne cette stack.",
|
|
785
789
|
"open": "Ouvrir l'assistant"
|
|
790
|
+
},
|
|
791
|
+
"envTest": {
|
|
792
|
+
"title": "Tester la création de l'environnement",
|
|
793
|
+
"hint": "Exécute tout le cycle de vie sur une branche jetable : créer la branche, provisionner, démonter, supprimer la branche.",
|
|
794
|
+
"start": "Tester la création de l'environnement",
|
|
795
|
+
"stop": "Arrêter",
|
|
796
|
+
"infraless": "Configurez un type de provisionnement ci-dessus pour tester la création de l'environnement.",
|
|
797
|
+
"running": "Test en cours : {stage}",
|
|
798
|
+
"succeeded": "Test réussi : l'environnement a été créé puis démonté, et la branche a été supprimée.",
|
|
799
|
+
"failed": "Échec du test",
|
|
800
|
+
"stage": {
|
|
801
|
+
"creating_branch": "création de la branche",
|
|
802
|
+
"provisioning": "provisionnement de l'environnement",
|
|
803
|
+
"tearing_down": "démontage de l'environnement",
|
|
804
|
+
"deleting_branch": "suppression de la branche",
|
|
805
|
+
"done": "terminé"
|
|
806
|
+
}
|
|
786
807
|
}
|
|
787
808
|
},
|
|
788
809
|
"agentConfig": {
|
package/i18n/locales/he.json
CHANGED
|
@@ -436,7 +436,11 @@
|
|
|
436
436
|
"visual_pipeline_no_frontend": "אין frontend לבדיקה",
|
|
437
437
|
"model_policy_blocked": "המודל נחסם על ידי מדיניות החשבון",
|
|
438
438
|
"model_policy_unsupported": "מדיניות המודלים אינה זמינה כאן",
|
|
439
|
-
"deployer_required_before_tester": "הוסף Deployer לפני ה-Tester"
|
|
439
|
+
"deployer_required_before_tester": "הוסף Deployer לפני ה-Tester",
|
|
440
|
+
"env_test_not_a_frame": "לא שירות",
|
|
441
|
+
"env_test_infraless": "אין מה לבדוק",
|
|
442
|
+
"env_test_not_provisionable": "מטפל הסביבה אינו מוגדר",
|
|
443
|
+
"env_test_no_vcs": "ספק Git אינו מחובר"
|
|
440
444
|
},
|
|
441
445
|
"fallbackMessage": "פעולה זו מתנגשת עם המצב הנוכחי.",
|
|
442
446
|
"providersUnconfigured": {
|
|
@@ -783,6 +787,23 @@
|
|
|
783
787
|
"title": "הגדרת סביבת Compose",
|
|
784
788
|
"hint": "הגדירו את המתכון, בדיקות המוכנות והמטפל כדי שה-Deployer יקצה את הסטאק הזה.",
|
|
785
789
|
"open": "פתיחת אשף"
|
|
790
|
+
},
|
|
791
|
+
"envTest": {
|
|
792
|
+
"title": "בדיקת יצירת סביבה",
|
|
793
|
+
"hint": "מריץ את כל מחזור החיים על ענף חד-פעמי: יצירת ענף, הקצאה, פירוק ומחיקת הענף.",
|
|
794
|
+
"start": "בדיקת יצירת סביבה",
|
|
795
|
+
"stop": "עצירה",
|
|
796
|
+
"infraless": "הגדירו סוג הקצאה למעלה כדי לבדוק את יצירת הסביבה.",
|
|
797
|
+
"running": "בבדיקה: {stage}",
|
|
798
|
+
"succeeded": "הבדיקה עברה: הסביבה נוצרה ופורקה, והענף נמחק.",
|
|
799
|
+
"failed": "הבדיקה נכשלה",
|
|
800
|
+
"stage": {
|
|
801
|
+
"creating_branch": "יוצר ענף",
|
|
802
|
+
"provisioning": "מקצה סביבה",
|
|
803
|
+
"tearing_down": "מפרק סביבה",
|
|
804
|
+
"deleting_branch": "מוחק ענף",
|
|
805
|
+
"done": "הושלם"
|
|
806
|
+
}
|
|
786
807
|
}
|
|
787
808
|
},
|
|
788
809
|
"agentConfig": {
|
package/i18n/locales/it.json
CHANGED
|
@@ -1093,6 +1093,23 @@
|
|
|
1093
1093
|
"title": "Configurazione ambiente Compose",
|
|
1094
1094
|
"hint": "Configura la ricetta, i preflight e l'handler affinche' il Deployer provisioni questo stack.",
|
|
1095
1095
|
"open": "Apri la procedura guidata"
|
|
1096
|
+
},
|
|
1097
|
+
"envTest": {
|
|
1098
|
+
"title": "Prova la creazione dell'ambiente",
|
|
1099
|
+
"hint": "Esegue l'intero ciclo di vita su un branch usa e getta: crea branch, provisiona, smonta, elimina branch.",
|
|
1100
|
+
"start": "Prova la creazione dell'ambiente",
|
|
1101
|
+
"stop": "Ferma",
|
|
1102
|
+
"infraless": "Configura sopra un tipo di provisioning per provare la creazione dell'ambiente.",
|
|
1103
|
+
"running": "Prova in corso: {stage}",
|
|
1104
|
+
"succeeded": "Prova superata: l'ambiente è stato creato e smontato, e il branch è stato eliminato.",
|
|
1105
|
+
"failed": "Prova fallita",
|
|
1106
|
+
"stage": {
|
|
1107
|
+
"creating_branch": "creazione del branch",
|
|
1108
|
+
"provisioning": "provisioning dell'ambiente",
|
|
1109
|
+
"tearing_down": "smontaggio dell'ambiente",
|
|
1110
|
+
"deleting_branch": "eliminazione del branch",
|
|
1111
|
+
"done": "completato"
|
|
1112
|
+
}
|
|
1096
1113
|
}
|
|
1097
1114
|
},
|
|
1098
1115
|
"agentConfig": {
|
|
@@ -3786,7 +3803,11 @@
|
|
|
3786
3803
|
"visual_pipeline_no_frontend": "Nessun frontend da testare",
|
|
3787
3804
|
"model_policy_blocked": "Modello bloccato dalla policy dell'account",
|
|
3788
3805
|
"model_policy_unsupported": "Policy dei modelli non disponibile qui",
|
|
3789
|
-
"deployer_required_before_tester": "Aggiungi un Deployer prima del Tester"
|
|
3806
|
+
"deployer_required_before_tester": "Aggiungi un Deployer prima del Tester",
|
|
3807
|
+
"env_test_not_a_frame": "Non è un servizio",
|
|
3808
|
+
"env_test_infraless": "Niente da testare",
|
|
3809
|
+
"env_test_not_provisionable": "Handler dell'ambiente non configurato",
|
|
3810
|
+
"env_test_no_vcs": "Provider Git non connesso"
|
|
3790
3811
|
},
|
|
3791
3812
|
"fallbackMessage": "Questa azione è in conflitto con lo stato attuale.",
|
|
3792
3813
|
"providersUnconfigured": {
|
package/i18n/locales/ja.json
CHANGED
|
@@ -436,7 +436,11 @@
|
|
|
436
436
|
"visual_pipeline_no_frontend": "テスト対象のフロントエンドがありません",
|
|
437
437
|
"model_policy_blocked": "アカウントのポリシーによりモデルがブロックされています",
|
|
438
438
|
"model_policy_unsupported": "モデルポリシーはここでは利用できません",
|
|
439
|
-
"deployer_required_before_tester": "テスターの前にDeployerを追加してください"
|
|
439
|
+
"deployer_required_before_tester": "テスターの前にDeployerを追加してください",
|
|
440
|
+
"env_test_not_a_frame": "サービスではありません",
|
|
441
|
+
"env_test_infraless": "テストする対象がありません",
|
|
442
|
+
"env_test_not_provisionable": "環境ハンドラーが設定されていません",
|
|
443
|
+
"env_test_no_vcs": "Git プロバイダーが未接続です"
|
|
440
444
|
},
|
|
441
445
|
"fallbackMessage": "この操作は現在の状態と競合します。",
|
|
442
446
|
"providersUnconfigured": {
|
|
@@ -783,6 +787,23 @@
|
|
|
783
787
|
"title": "Compose 環境のセットアップ",
|
|
784
788
|
"hint": "レシピ・プリフライト・ハンドラーを設定して、Deployer がこのスタックをプロビジョニングできるようにします。",
|
|
785
789
|
"open": "ウィザードを開く"
|
|
790
|
+
},
|
|
791
|
+
"envTest": {
|
|
792
|
+
"title": "環境作成のテスト",
|
|
793
|
+
"hint": "使い捨てブランチに対してライフサイクル全体を実行します。ブランチ作成、プロビジョニング、破棄、ブランチ削除の順に行います。",
|
|
794
|
+
"start": "環境作成のテスト",
|
|
795
|
+
"stop": "停止",
|
|
796
|
+
"infraless": "環境作成をテストするには、上でプロビジョニングの種類を設定してください。",
|
|
797
|
+
"running": "テスト中: {stage}",
|
|
798
|
+
"succeeded": "テスト成功: 環境が作成されて破棄され、ブランチが削除されました。",
|
|
799
|
+
"failed": "テスト失敗",
|
|
800
|
+
"stage": {
|
|
801
|
+
"creating_branch": "ブランチを作成中",
|
|
802
|
+
"provisioning": "環境をプロビジョニング中",
|
|
803
|
+
"tearing_down": "環境を破棄中",
|
|
804
|
+
"deleting_branch": "ブランチを削除中",
|
|
805
|
+
"done": "完了"
|
|
806
|
+
}
|
|
786
807
|
}
|
|
787
808
|
},
|
|
788
809
|
"agentConfig": {
|
package/i18n/locales/pl.json
CHANGED
|
@@ -436,7 +436,11 @@
|
|
|
436
436
|
"visual_pipeline_no_frontend": "Brak frontendu do przetestowania",
|
|
437
437
|
"model_policy_blocked": "Model zablokowany przez politykę konta",
|
|
438
438
|
"model_policy_unsupported": "Polityka modeli jest tu niedostępna",
|
|
439
|
-
"deployer_required_before_tester": "Dodaj Deployer przed Testerem"
|
|
439
|
+
"deployer_required_before_tester": "Dodaj Deployer przed Testerem",
|
|
440
|
+
"env_test_not_a_frame": "To nie usługa",
|
|
441
|
+
"env_test_infraless": "Nie ma czego testować",
|
|
442
|
+
"env_test_not_provisionable": "Handler środowiska nie jest skonfigurowany",
|
|
443
|
+
"env_test_no_vcs": "Dostawca Git nie jest połączony"
|
|
440
444
|
},
|
|
441
445
|
"fallbackMessage": "Ta akcja jest sprzeczna z bieżącym stanem.",
|
|
442
446
|
"providersUnconfigured": {
|
|
@@ -783,6 +787,23 @@
|
|
|
783
787
|
"title": "Konfiguracja środowiska Compose",
|
|
784
788
|
"hint": "Skonfiguruj przepis, kontrole wstępne i handler, aby Deployer udostępnił ten stos.",
|
|
785
789
|
"open": "Otwórz kreatora"
|
|
790
|
+
},
|
|
791
|
+
"envTest": {
|
|
792
|
+
"title": "Testowanie tworzenia środowiska",
|
|
793
|
+
"hint": "Uruchamia cały cykl życia na jednorazowej gałęzi: utworzenie gałęzi, udostępnienie, usunięcie zasobów, usunięcie gałęzi.",
|
|
794
|
+
"start": "Testowanie tworzenia środowiska",
|
|
795
|
+
"stop": "Zatrzymaj",
|
|
796
|
+
"infraless": "Skonfiguruj powyżej typ udostępniania, aby przetestować tworzenie środowiska.",
|
|
797
|
+
"running": "Testowanie: {stage}",
|
|
798
|
+
"succeeded": "Test zaliczony: środowisko zostało utworzone i usunięte, a gałąź została skasowana.",
|
|
799
|
+
"failed": "Test nie powiódł się",
|
|
800
|
+
"stage": {
|
|
801
|
+
"creating_branch": "tworzenie gałęzi",
|
|
802
|
+
"provisioning": "udostępnianie środowiska",
|
|
803
|
+
"tearing_down": "usuwanie środowiska",
|
|
804
|
+
"deleting_branch": "usuwanie gałęzi",
|
|
805
|
+
"done": "gotowe"
|
|
806
|
+
}
|
|
786
807
|
}
|
|
787
808
|
},
|
|
788
809
|
"agentConfig": {
|
package/i18n/locales/tr.json
CHANGED
|
@@ -436,7 +436,11 @@
|
|
|
436
436
|
"visual_pipeline_no_frontend": "Test edilecek bir frontend yok",
|
|
437
437
|
"model_policy_blocked": "Model, hesap politikası tarafından engellendi",
|
|
438
438
|
"model_policy_unsupported": "Model politikası burada kullanılamıyor",
|
|
439
|
-
"deployer_required_before_tester": "Tester’dan önce bir Deployer ekleyin"
|
|
439
|
+
"deployer_required_before_tester": "Tester’dan önce bir Deployer ekleyin",
|
|
440
|
+
"env_test_not_a_frame": "Bir hizmet değil",
|
|
441
|
+
"env_test_infraless": "Test edilecek bir şey yok",
|
|
442
|
+
"env_test_not_provisionable": "Ortam işleyicisi yapılandırılmamış",
|
|
443
|
+
"env_test_no_vcs": "Git sağlayıcısı bağlı değil"
|
|
440
444
|
},
|
|
441
445
|
"fallbackMessage": "Bu eylem mevcut durumla çelişiyor.",
|
|
442
446
|
"providersUnconfigured": {
|
|
@@ -783,6 +787,23 @@
|
|
|
783
787
|
"title": "Compose ortam kurulumu",
|
|
784
788
|
"hint": "Tarifi, ön kontrolleri ve işleyiciyi yapılandırın; böylece Deployer bu yığını sağlar.",
|
|
785
789
|
"open": "Sihirbazı aç"
|
|
790
|
+
},
|
|
791
|
+
"envTest": {
|
|
792
|
+
"title": "Ortam oluşturmayı test et",
|
|
793
|
+
"hint": "Tek kullanımlık bir dal üzerinde tüm yaşam döngüsünü çalıştırır: dal oluştur, sağla, kaldır, dalı sil.",
|
|
794
|
+
"start": "Ortam oluşturmayı test et",
|
|
795
|
+
"stop": "Durdur",
|
|
796
|
+
"infraless": "Ortam oluşturmayı test etmek için yukarıdan bir sağlama türü yapılandırın.",
|
|
797
|
+
"running": "Test ediliyor: {stage}",
|
|
798
|
+
"succeeded": "Test başarılı: ortam oluşturuldu ve kaldırıldı, dal silindi.",
|
|
799
|
+
"failed": "Test başarısız",
|
|
800
|
+
"stage": {
|
|
801
|
+
"creating_branch": "dal oluşturuluyor",
|
|
802
|
+
"provisioning": "ortam sağlanıyor",
|
|
803
|
+
"tearing_down": "ortam kaldırılıyor",
|
|
804
|
+
"deleting_branch": "dal siliniyor",
|
|
805
|
+
"done": "tamamlandı"
|
|
806
|
+
}
|
|
786
807
|
}
|
|
787
808
|
},
|
|
788
809
|
"agentConfig": {
|
package/i18n/locales/uk.json
CHANGED
|
@@ -436,7 +436,11 @@
|
|
|
436
436
|
"visual_pipeline_no_frontend": "Немає фронтенду для тестування",
|
|
437
437
|
"model_policy_blocked": "Модель заблоковано політикою облікового запису",
|
|
438
438
|
"model_policy_unsupported": "Політика моделей тут недоступна",
|
|
439
|
-
"deployer_required_before_tester": "Додайте Deployer перед Tester"
|
|
439
|
+
"deployer_required_before_tester": "Додайте Deployer перед Tester",
|
|
440
|
+
"env_test_not_a_frame": "Не є сервісом",
|
|
441
|
+
"env_test_infraless": "Немає чого тестувати",
|
|
442
|
+
"env_test_not_provisionable": "Обробник середовища не налаштовано",
|
|
443
|
+
"env_test_no_vcs": "Провайдер Git не підключено"
|
|
440
444
|
},
|
|
441
445
|
"fallbackMessage": "Ця дія суперечить поточному стану.",
|
|
442
446
|
"providersUnconfigured": {
|
|
@@ -783,6 +787,23 @@
|
|
|
783
787
|
"title": "Налаштування середовища Compose",
|
|
784
788
|
"hint": "Налаштуйте рецепт, попередні перевірки та обробник, щоб Deployer забезпечив цей стек.",
|
|
785
789
|
"open": "Відкрити майстер"
|
|
790
|
+
},
|
|
791
|
+
"envTest": {
|
|
792
|
+
"title": "Тестування створення середовища",
|
|
793
|
+
"hint": "Виконує весь життєвий цикл на одноразовій гілці: створити гілку, забезпечити, згорнути, видалити гілку.",
|
|
794
|
+
"start": "Тестування створення середовища",
|
|
795
|
+
"stop": "Зупинити",
|
|
796
|
+
"infraless": "Налаштуйте тип забезпечення вище, щоб протестувати створення середовища.",
|
|
797
|
+
"running": "Тестування: {stage}",
|
|
798
|
+
"succeeded": "Тест пройдено: середовище було створено та згорнуто, а гілку видалено.",
|
|
799
|
+
"failed": "Тест не пройдено",
|
|
800
|
+
"stage": {
|
|
801
|
+
"creating_branch": "створення гілки",
|
|
802
|
+
"provisioning": "забезпечення середовища",
|
|
803
|
+
"tearing_down": "згортання середовища",
|
|
804
|
+
"deleting_branch": "видалення гілки",
|
|
805
|
+
"done": "готово"
|
|
806
|
+
}
|
|
786
807
|
}
|
|
787
808
|
},
|
|
788
809
|
"agentConfig": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.116.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"valibot": "^1.4.2",
|
|
35
35
|
"vue": "3.5.39",
|
|
36
36
|
"wretch": "^3.0.9",
|
|
37
|
-
"@cat-factory/contracts": "0.
|
|
37
|
+
"@cat-factory/contracts": "0.128.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|