@cat-factory/app 0.229.0 → 0.231.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/README.md CHANGED
@@ -583,7 +583,7 @@ event left to restore it.
583
583
  All user-facing SPA copy goes through `@nuxtjs/i18n`; never hard-code a display string. This
584
584
  layer ships the base `en` locale, and a downstream deployment overrides by dropping its own files
585
585
  (the per-layer deep-merge is the override seam, consumer wins key by key). Migration status:
586
- [`docs/localization.md`](../../docs/localization.md).
586
+ [`docs/internal/localization.md`](../../docs/internal/localization.md).
587
587
 
588
588
  - `i18n/locales/<locale>.json`: the catalogs (the v9+ `i18n/` convention, NOT `app/locales/`).
589
589
  - `i18n/i18n.config.ts`: runtime vue-i18n behaviour only (fallback locale, the plural
@@ -4,29 +4,37 @@ import { apiErrorEnvelope } from '~/composables/api/errors'
4
4
  import SecretInput from '~/components/common/SecretInput.vue'
5
5
  import type { VcsProvider } from '~/types/domain'
6
6
  import { VCS_PROVIDER_ICONS, VCS_PROVIDER_LABELS, VCS_PROVIDER_TOKEN_URLS } from '~/utils/vcs'
7
+ import { SSO_ERROR_MESSAGE_KEYS } from '~/utils/sso'
7
8
 
8
9
  const auth = useAuthStore()
9
10
  const { t } = useI18n()
10
11
 
11
- // Local-mode source-control PAT login. The PAT lives server-side in env (GITHUB_PAT /
12
- // GITLAB_PAT); the login screen only SELECTS a configured provider no token is ever typed
13
- // into or shown in the browser. The brand labels / icons / token-settings URLs are the shared
14
- // provider descriptors in `~/utils/vcs` (brand names stay verbatim across locales, so they are
15
- // constants rather than catalog keys — the same convention as ApiKeysSection). The "create a
16
- // token" link prefers the server's scopes-preselected deep link (`patLogin.setupUrls`); the
17
- // descriptor's URL is the fallback.
12
+ // Local-mode source-control PAT login. A configured token lives server-side and is selected by
13
+ // PROVIDER (no token is typed into the browser); a deployment that holds none can be handed one
14
+ // here, and it becomes both this sign-in and the credential the deployment operates with. The
15
+ // brand labels / icons / token-settings URLs are the shared provider descriptors in `~/utils/vcs`
16
+ // (brand names stay verbatim across locales, so they are constants rather than catalog keys — the
17
+ // same convention as ApiKeysSection). The "create a token" link prefers the server's
18
+ // scopes-preselected deep link (`patLogin.setupUrls`); the descriptor's URL is the fallback.
18
19
  type PatProvider = VcsProvider
20
+ /** Every provider a token page exists for — the fallback link set when none can be installed. */
19
21
  const ALL_PROVIDERS: PatProvider[] = ['github', 'gitlab']
20
22
  const PROVIDER_LABELS = VCS_PROVIDER_LABELS
21
23
  const PROVIDER_ICONS = VCS_PROVIDER_ICONS
22
24
  const PROVIDER_TOKEN_URLS = VCS_PROVIDER_TOKEN_URLS
23
25
 
24
26
  const patLoginCfg = computed(() => auth.localMode?.patLogin)
25
- // Only providers whose PAT is configured in env can sign in (the token is the operational
26
- // credential too). A provider without one gets no button — see the no-PAT notice instead.
27
+ // Providers whose token the deployment already holds: one-click sign-in. A provider without one
28
+ // gets no button — it is offered in the paste form below instead.
27
29
  const configuredProviders = computed<PatProvider[]>(
28
30
  () => (patLoginCfg.value?.configured ?? []) as PatProvider[],
29
31
  )
32
+ // Providers the server will ACCEPT a token for from here. Empty when `.env` owns the credential
33
+ // (it wins, so a pasted token would be ignored) or nothing can seal one — the notice then falls
34
+ // back to telling the developer where the token actually has to go.
35
+ const installableProviders = computed<PatProvider[]>(
36
+ () => (patLoginCfg.value?.installable ?? []) as PatProvider[],
37
+ )
30
38
  const isLocalMode = computed(() => auth.localMode?.enabled === true)
31
39
  const hasConfiguredPat = computed(() => configuredProviders.value.length > 0)
32
40
  // Mothership mode: identity + org data live on a hosted mothership, so the primary sign-in is a
@@ -124,13 +132,27 @@ const showOAuthDivider = computed(
124
132
  () => auth.providers.password && (auth.providers.github || auth.providers.google),
125
133
  )
126
134
 
127
- // Hosted (remote node) PAT login: the user pastes their OWN source-control PAT, which the
128
- // server resolves to an account and holds to its login/org/domain allowlist. The available
129
- // providers come from the server (`auth.patProviders`) GitHub always, GitLab when configured,
130
- // on both hosted facades (Node + Worker); empty in local mode (which uses the configured-token
131
- // flow above).
135
+ // Enterprise SSO. Led with, above the consumer providers, because on a deployment that configures
136
+ // it that is the intended way in a person arriving at an org's board should not have to find
137
+ // their company's button under two they must not use. The label is the operator's own wording
138
+ // (it names their IdP), so it is rendered verbatim rather than through the catalog.
139
+ const ssoLabel = computed(() => auth.sso?.label ?? '')
140
+ // Translated copy for a refused round-trip, keyed off the machine-readable reason the backend
141
+ // handed back. Each reason has its own wording because the remedies differ: a missing directory
142
+ // group is something the user takes to IT, a failed code exchange is the operator's own config.
143
+ const ssoErrorMessage = computed(() =>
144
+ auth.ssoError ? t(SSO_ERROR_MESSAGE_KEYS[auth.ssoError]) : null,
145
+ )
146
+
147
+ // Paste-a-token sign-in. The user supplies a source-control PAT and the server resolves it to an
148
+ // account. What it MEANS differs per facade, which is why the providers come from two places:
149
+ // - hosted (remote node): the user's OWN token, held to the login/org/domain allowlist. GitHub
150
+ // always, GitLab when configured, on both hosted facades (Node + Worker).
151
+ // - local: the token also becomes the DEPLOYMENT's credential, so the server names the providers
152
+ // it will accept one for (`installable`) — empty once `.env` owns it.
153
+ // One form serves both; the local-only hint below says what the token is additionally used for.
132
154
  const remotePatProviders = computed<PatProvider[]>(() =>
133
- isLocalMode.value ? [] : (auth.patProviders as PatProvider[]),
155
+ isLocalMode.value ? installableProviders.value : (auth.patProviders as PatProvider[]),
134
156
  )
135
157
  const remotePatProvider = ref<PatProvider>('github')
136
158
  watch(
@@ -157,6 +179,16 @@ async function submitRemotePat() {
157
179
  }
158
180
  }
159
181
 
182
+ // Only divide SSO from what follows when something actually follows it.
183
+ const showSsoDivider = computed(
184
+ () =>
185
+ auth.providers.sso &&
186
+ (auth.providers.github ||
187
+ auth.providers.google ||
188
+ auth.providers.password ||
189
+ remotePatProviders.value.length > 0),
190
+ )
191
+
160
192
  // A remote deployment (node service / Worker) that advertises no sign-in method at all:
161
193
  // no OAuth, no password, no PAT, and not local mode. The auth gate still routes here (a
162
194
  // remote facade has no anonymous tier), so instead of a blank card we explain that
@@ -167,6 +199,7 @@ const noSignInMethod = computed(
167
199
  !auth.providers.github &&
168
200
  !auth.providers.google &&
169
201
  !auth.providers.password &&
202
+ !auth.providers.sso &&
170
203
  remotePatProviders.value.length === 0,
171
204
  )
172
205
  </script>
@@ -227,16 +260,24 @@ const noSignInMethod = computed(
227
260
  {{ t('auth.localMode.continueWithConfigured', { provider: PROVIDER_LABELS[p] }) }}
228
261
  </UButton>
229
262
 
230
- <!-- Neither GITHUB_PAT nor GITLAB_PAT is set: tell the developer how to configure one -->
263
+ <!-- The deployment holds no token. When one can be installed from here the notice says
264
+ so and the create-token links feed the form below; when it can't (`.env` owns the
265
+ credential, or nothing can seal one) it names where the token has to go instead. -->
231
266
  <template v-if="!hasConfiguredPat">
232
267
  <UAlert
233
268
  color="warning"
234
269
  variant="subtle"
235
270
  icon="i-lucide-key-round"
236
271
  :title="t('auth.localMode.noPatTitle')"
237
- :description="t('auth.localMode.noPatBody')"
272
+ :description="
273
+ installableProviders.length > 0
274
+ ? t('auth.localMode.setupBody')
275
+ : t('auth.localMode.noPatBody')
276
+ "
238
277
  />
239
- <div class="flex flex-wrap gap-3 px-1">
278
+ <!-- Only when nothing can be installed here: the paste form below carries its own
279
+ per-provider link, so showing these too would offer the same thing twice. -->
280
+ <div v-if="installableProviders.length === 0" class="flex flex-wrap gap-3 px-1">
240
281
  <a
241
282
  v-for="p in ALL_PROVIDERS"
242
283
  :key="p"
@@ -261,6 +302,41 @@ const noSignInMethod = computed(
261
302
  <span class="h-px flex-1 bg-slate-800" />
262
303
  </div>
263
304
 
305
+ <!-- A refused SSO round-trip: name the rule that refused, don't return the user to an
306
+ unchanged sign-in button. -->
307
+ <UAlert
308
+ v-if="ssoErrorMessage && mode !== 'forgot'"
309
+ class="mb-4"
310
+ color="error"
311
+ variant="subtle"
312
+ icon="i-lucide-shield-x"
313
+ :title="t('auth.sso.failedTitle')"
314
+ :description="ssoErrorMessage"
315
+ data-testid="sso-error"
316
+ />
317
+
318
+ <!-- Enterprise SSO: the deployment's OWN identity provider, led with where configured. -->
319
+ <div v-if="auth.providers.sso && mode !== 'forgot'" class="mb-2 space-y-2">
320
+ <UButton
321
+ block
322
+ size="lg"
323
+ color="primary"
324
+ icon="i-lucide-building-2"
325
+ data-testid="sso-signin"
326
+ @click="auth.loginWithSso(invite)"
327
+ >
328
+ {{ t('auth.sso.continueWith', { provider: ssoLabel }) }}
329
+ </UButton>
330
+ </div>
331
+
332
+ <div
333
+ v-if="showSsoDivider && mode !== 'forgot'"
334
+ class="my-4 flex items-center gap-3 text-xs text-slate-500"
335
+ >
336
+ <span class="h-px flex-1 bg-slate-800" /> {{ t('auth.login.or') }}
337
+ <span class="h-px flex-1 bg-slate-800" />
338
+ </div>
339
+
264
340
  <!-- OAuth providers -->
265
341
  <div v-if="mode !== 'forgot'" class="space-y-2">
266
342
  <UButton
@@ -364,10 +440,16 @@ const noSignInMethod = computed(
364
440
  </p>
365
441
  </form>
366
442
 
367
- <!-- Hosted (remote node) PAT login: paste your own source-control PAT -->
443
+ <!-- Paste-a-token sign-in: your own PAT on a hosted node; on local mode the token this
444
+ deployment will operate with (see `remotePatProviders`). -->
368
445
  <template v-if="remotePatProviders.length > 0 && mode !== 'forgot'">
369
446
  <div
370
- v-if="auth.providers.github || auth.providers.google || auth.providers.password"
447
+ v-if="
448
+ auth.providers.github ||
449
+ auth.providers.google ||
450
+ auth.providers.password ||
451
+ hasConfiguredPat
452
+ "
371
453
  class="my-4 flex items-center gap-3 text-xs text-slate-500"
372
454
  >
373
455
  <span class="h-px flex-1 bg-slate-800" /> {{ t('auth.login.or') }}
@@ -412,6 +494,11 @@ const noSignInMethod = computed(
412
494
  >
413
495
  {{ t('auth.login.signInWithPat', { provider: PROVIDER_LABELS[remotePatProvider] }) }}
414
496
  </UButton>
497
+ <!-- Local mode only: say what else the token is for BEFORE it is handed over, since it
498
+ becomes the credential every agent step on this machine clones and pushes with. -->
499
+ <p v-if="isLocalMode" class="px-1 text-xs text-slate-400">
500
+ {{ t('auth.localMode.tokenBecomesCredential') }}
501
+ </p>
415
502
  <p class="px-1 text-center">
416
503
  <a
417
504
  :href="tokenCreateUrl(remotePatProvider)"
@@ -895,6 +895,7 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
895
895
  : t('board.addTask.descriptionPlaceholder')
896
896
  "
897
897
  class="w-full"
898
+ data-testid="add-task-description"
898
899
  />
899
900
  </UFormField>
900
901
  </template>
@@ -406,6 +406,7 @@ function selectTask() {
406
406
  :icon="!runnable ? 'i-lucide-lock' : sandboxed ? 'i-lucide-shield' : 'i-lucide-play'"
407
407
  :loading="starting"
408
408
  :disabled="!runnable || starting"
409
+ data-testid="task-start"
409
410
  :title="
410
411
  !runnable
411
412
  ? t('board.task.waitingOn', { deps: unmet.map((d) => d.title).join(', ') })
@@ -148,6 +148,9 @@ async function resolve(choice: 'recheck' | 'proceed') {
148
148
  v-for="issue in issues"
149
149
  :key="`${issue.code}:${issue.field?.key ?? ''}`"
150
150
  class="flex items-start gap-2 text-xs"
151
+ data-testid="input-gate-issue"
152
+ :data-issue-code="issue.code"
153
+ :data-issue-severity="issue.severity"
151
154
  >
152
155
  <UBadge
153
156
  :color="issue.severity === 'blocking' ? 'warning' : 'neutral'"
@@ -170,6 +170,17 @@ async function act(choice: 'proceed' | 'bounce' | 'stop') {
170
170
  </span>
171
171
  </div>
172
172
 
173
+ <!-- The rubric asked for a model this deployment cannot serve, so something else scored
174
+ the work. Only this case is shown: a pin that was honoured says nothing the model
175
+ name doesn't, and being overridden by the task's own choice is the normal outcome. -->
176
+ <p
177
+ v-if="judge.modelPin?.status === 'unavailable'"
178
+ class="mt-2 rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-2 text-[12px] leading-relaxed text-amber-200"
179
+ data-testid="judge-model-pin"
180
+ >
181
+ {{ t('judge.modelPinUnavailable', { model: judge.modelPin.requested }) }}
182
+ </p>
183
+
173
184
  <!-- Why the judge did nothing, when it did nothing. A skipped judge must never read
174
185
  like a clean pass. -->
175
186
  <p
@@ -288,6 +299,8 @@ async function act(choice: 'proceed' | 'bounce' | 'stop') {
288
299
  v-for="round in rounds"
289
300
  :key="round.round"
290
301
  class="flex flex-wrap items-center gap-2 rounded-md border border-slate-800 bg-slate-950/40 px-3 py-1.5 text-[12px] text-slate-300"
302
+ data-testid="judge-round"
303
+ :data-round-disposition="round.disposition"
291
304
  >
292
305
  <span class="text-slate-500">{{ t('judge.round', { round: round.round }) }}</span>
293
306
  <span class="font-medium text-slate-100">{{
@@ -373,6 +373,7 @@ async function mergePr() {
373
373
  <button
374
374
  type="button"
375
375
  class="flex min-w-0 cursor-pointer items-center gap-2 text-start transition hover:text-white"
376
+ data-testid="run-step-open"
376
377
  :title="
377
378
  s.output
378
379
  ? t('inspector.execution.viewDetailsOutput')
@@ -708,6 +708,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
708
708
  <div
709
709
  v-if="incorporated"
710
710
  class="mb-4 flex items-center gap-2 rounded-lg border border-emerald-900/60 bg-emerald-950/30 p-4 text-sm text-emerald-300"
711
+ data-testid="requirements-settled"
711
712
  >
712
713
  <UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
713
714
  {{ t('requirements.settled') }}
@@ -760,6 +761,9 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
760
761
  <div
761
762
  class="rounded-lg border border-slate-800 bg-slate-900/60 p-3"
762
763
  :class="{ 'opacity-60': item.status === 'dismissed' }"
764
+ data-testid="requirements-finding"
765
+ :data-finding-status="item.status"
766
+ :data-finding-severity="item.severity"
763
767
  >
764
768
  <div class="flex items-start gap-2">
765
769
  <UIcon
@@ -831,6 +835,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
831
835
  size="xs"
832
836
  :icon="opt.icon"
833
837
  :disabled="frozen"
838
+ :data-testid="`requirements-mode-${opt.mode}`"
834
839
  @click="setMode(item, opt.mode)"
835
840
  >
836
841
  {{ t(opt.labelKey) }}
@@ -881,6 +886,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
881
886
  class="mt-2 w-full"
882
887
  :placeholder="t('requirements.answerPlaceholder')"
883
888
  :disabled="frozen"
889
+ data-testid="requirements-answer"
884
890
  @blur="persistDraft(item)"
885
891
  />
886
892
  <p
@@ -1170,6 +1176,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
1170
1176
  :loading="acting"
1171
1177
  :disabled="!access.canExecuteRuns.value"
1172
1178
  :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
1179
+ data-testid="requirements-proceed"
1173
1180
  @click="proceed"
1174
1181
  >
1175
1182
  {{ t('requirements.actions.proceedNothing') }}
@@ -1183,6 +1190,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
1183
1190
  :loading="reworking"
1184
1191
  :disabled="!canIncorporate || !access.canExecuteRuns.value"
1185
1192
  :title="access.canExecuteRuns.value ? undefined : t('access.noRunExecute')"
1193
+ data-testid="requirements-incorporate"
1186
1194
  @click="incorporate()"
1187
1195
  >
1188
1196
  {{ t('requirements.actions.incorporateAnswers') }}
@@ -213,8 +213,8 @@ suite can address a row and the caption inside it.
213
213
  Together, `fields` + `defaultFragmentIds` + `defaultPipelineId` are what turns a task type from a
214
214
  badge into a **reusable operation**: a canned unit of work an org runs repeatedly with per-case
215
215
  input, whose collected values reach every agent's prompt. See
216
- [`docs/initiatives/reusable-operations.md`](../../../../docs/initiatives/reusable-operations.md)
217
- and the `org:introduce-api` worked example in `backend/internal/example-custom-agent`.
216
+ [`backend/docs/reusable-operations.md`](../../../../backend/docs/reusable-operations.md) and the
217
+ `org:introduce-api` worked example in `backend/internal/example-custom-agent`.
218
218
 
219
219
  The **same type can be delivered from the backend** instead of code-shipped: register it on the
220
220
  deployment's app-owned `TaskTypeRegistry` and it arrives in the workspace snapshot's
@@ -1,6 +1,8 @@
1
1
  import type { Ref } from 'vue'
2
+ import { SSO_ERROR_FRAGMENT_KEY, parseSsoErrorReason } from '@cat-factory/contracts'
2
3
  import type { LocalModeConfig } from '@cat-factory/contracts'
3
4
  import type { AuthUser } from '~/types/domain'
5
+ import type { SsoLoginFailure } from '~/utils/sso'
4
6
 
5
7
  /**
6
8
  * Shared reactive state + injected dependencies the auth-store redirect factory closes over.
@@ -13,6 +15,8 @@ export interface AuthRedirectContext {
13
15
  token: Ref<string | null>
14
16
  localMode: Ref<LocalModeConfig | null>
15
17
  mothershipError: Ref<string | null>
18
+ /** Why the last enterprise-SSO round-trip produced no session, when one was refused. */
19
+ ssoError: Ref<SsoLoginFailure | null>
16
20
  /** Apply a freshly-minted token + user (the session factory's setter). */
17
21
  applySession: (result: { token: string; user: AuthUser }) => void
18
22
  }
@@ -23,7 +27,7 @@ export interface AuthRedirectContext {
23
27
  * mode — exchanging a returning MOTHERSHIP session for a local one.
24
28
  */
25
29
  export function createAuthRedirectActions(ctx: AuthRedirectContext) {
26
- const { api, token, localMode, mothershipError, applySession } = ctx
30
+ const { api, token, localMode, mothershipError, ssoError, applySession } = ctx
27
31
 
28
32
  /** Pull a token handed back in the post-login URL fragment (#token=…). */
29
33
  function consumeRedirectToken() {
@@ -35,6 +39,27 @@ export function createAuthRedirectActions(ctx: AuthRedirectContext) {
35
39
  history.replaceState(null, '', window.location.pathname + window.location.search)
36
40
  }
37
41
 
42
+ /**
43
+ * Pull the reason a REFUSED enterprise-SSO round-trip handed back (`#sso_error=<reason>`), the
44
+ * sibling of the `#token=` a successful one sets.
45
+ *
46
+ * A fragment rather than a query for the same reason the token is: it never reaches a server log
47
+ * or a `Referer`, and the reason names the deployment's own admission rules. Stripped from the
48
+ * URL afterwards so a reload doesn't resurrect a stale failure.
49
+ */
50
+ function consumeSsoError() {
51
+ if (typeof window === 'undefined') return
52
+ const match = new RegExp(`(?:^#|[#&])${SSO_ERROR_FRAGMENT_KEY}=([^&]+)`).exec(
53
+ window.location.hash,
54
+ )
55
+ if (!match) return
56
+ // An unrecognised value came from a newer backend than this build: reported as `unknown`
57
+ // rather than rendered raw, and never dropped — the user clicked a button and is owed an
58
+ // answer about why they are back here.
59
+ ssoError.value = parseSsoErrorReason(decodeURIComponent(match[1]!)) ?? 'unknown'
60
+ history.replaceState(null, '', window.location.pathname + window.location.search)
61
+ }
62
+
38
63
  /**
39
64
  * Mothership mode: when the mothership OAuth redirect returns here (flagged
40
65
  * `?mothership_connect=1`), the URL fragment carries a MOTHERSHIP session — not a local one.
@@ -101,6 +126,7 @@ export function createAuthRedirectActions(ctx: AuthRedirectContext) {
101
126
 
102
127
  return {
103
128
  consumeRedirectToken,
129
+ consumeSsoError,
104
130
  maybeConnectMothership,
105
131
  signInViaMothership,
106
132
  maybeAcceptInvite,
@@ -44,6 +44,16 @@ export function createAuthSessionActions(ctx: AuthSessionContext) {
44
44
  window.location.href = `${apiBase}/auth/google/login?${redirectTarget(invite)}`
45
45
  }
46
46
 
47
+ /**
48
+ * Send the browser to the deployment's OWN identity provider (enterprise SSO), returning here
49
+ * after. One entry point whichever IdP is configured: the backend resolves the provider from its
50
+ * discovery document, so there is nothing per-vendor for the SPA to know.
51
+ */
52
+ function loginWithSso(invite?: string) {
53
+ if (typeof window === 'undefined') return
54
+ window.location.href = `${apiBase}/auth/sso/login?${redirectTarget(invite)}`
55
+ }
56
+
47
57
  /** Apply a freshly-minted token + user (from password signup/login). */
48
58
  function applySession(result: { token: string; user: AuthUser }) {
49
59
  token.value = result.token
@@ -107,6 +117,7 @@ export function createAuthSessionActions(ctx: AuthSessionContext) {
107
117
  applySession,
108
118
  login,
109
119
  loginWithGoogle,
120
+ loginWithSso,
110
121
  signup,
111
122
  passwordLogin,
112
123
  patLogin,
@@ -0,0 +1,64 @@
1
+ import { ref } from 'vue'
2
+ import { beforeEach, describe, expect, it } from 'vitest'
3
+ import { createAuthRedirectActions } from './mothership'
4
+ import type { SsoLoginFailure } from '~/utils/sso'
5
+
6
+ // The other half of the SSO refusal contract: the backend lands the browser here with a
7
+ // machine-readable reason in the fragment, and this is what turns it into state the login screen
8
+ // renders. Worth pinning because the failure mode is silent — a reason the SPA drops leaves the
9
+ // user back on the same sign-in button with no explanation for the click that just failed.
10
+
11
+ /** Point `window.location` + `history` at a URL the consumer can read and rewrite. */
12
+ function setUrl(path: string): void {
13
+ history.replaceState(null, '', path)
14
+ }
15
+
16
+ function actions() {
17
+ const ssoError = ref<SsoLoginFailure | null>(null)
18
+ const factory = createAuthRedirectActions({
19
+ api: {} as never,
20
+ token: ref<string | null>(null),
21
+ localMode: ref(null),
22
+ mothershipError: ref<string | null>(null),
23
+ ssoError,
24
+ applySession: () => {},
25
+ })
26
+ return { ...factory, ssoError }
27
+ }
28
+
29
+ describe('consumeSsoError', () => {
30
+ beforeEach(() => setUrl('/'))
31
+
32
+ it('reads a known reason off the fragment', () => {
33
+ setUrl('/#sso_error=group_required')
34
+ const a = actions()
35
+ a.consumeSsoError()
36
+ expect(a.ssoError.value).toBe('group_required')
37
+ })
38
+
39
+ it('strips the fragment so a reload does not resurrect a stale failure', () => {
40
+ setUrl('/board?ws=ws_1#sso_error=state_invalid')
41
+ actions().consumeSsoError()
42
+ expect(window.location.hash).toBe('')
43
+ // The rest of the URL is left alone.
44
+ expect(window.location.pathname + window.location.search).toBe('/board?ws=ws_1')
45
+ })
46
+
47
+ it('reports an unrecognised reason as `unknown` rather than dropping it', () => {
48
+ // A reason from a NEWER backend than this build. Rendering the raw wire token to a user is
49
+ // wrong; showing nothing after a failed sign-in is worse.
50
+ setUrl('/#sso_error=reason_from_the_future')
51
+ const a = actions()
52
+ a.consumeSsoError()
53
+ expect(a.ssoError.value).toBe('unknown')
54
+ })
55
+
56
+ it('leaves the state untouched when the fragment carries no SSO error', () => {
57
+ setUrl('/#token=abc')
58
+ const a = actions()
59
+ a.consumeSsoError()
60
+ expect(a.ssoError.value).toBeNull()
61
+ // The session token is another consumer's to read, so the fragment must survive.
62
+ expect(window.location.hash).toBe('#token=abc')
63
+ })
64
+ })
@@ -2,6 +2,7 @@ import type {
2
2
  BackendMisconfigured,
3
3
  InfrastructureCapabilities,
4
4
  LocalModeConfig,
5
+ SsoConfigView,
5
6
  } from '@cat-factory/contracts'
6
7
  import { defineStore } from 'pinia'
7
8
  import { computed, ref } from 'vue'
@@ -9,6 +10,7 @@ import type { AuthUser } from '~/types/domain'
9
10
  import { retryWhileBackendUnreachable } from '~/utils/backendReady'
10
11
  import { createAuthSessionActions } from '~/stores/auth/session'
11
12
  import { createAuthRedirectActions } from '~/stores/auth/mothership'
13
+ import type { SsoLoginFailure } from '~/utils/sso'
12
14
 
13
15
  /**
14
16
  * "Login with GitHub" session state. The backend mints a signed session token
@@ -31,7 +33,20 @@ export const useAuthStore = defineStore(
31
33
  /** Whether the backend requires authentication. */
32
34
  const required = ref(false)
33
35
  /** Which login providers the backend offers (drives the login UI). */
34
- const providers = ref({ github: false, password: false, google: false })
36
+ const providers = ref({ github: false, password: false, google: false, sso: false })
37
+ /**
38
+ * Presentation for the deployment's OWN identity provider (enterprise SSO) — its
39
+ * operator-supplied label and protocol. Null unless `providers.sso` is set. The label names
40
+ * the operator's IdP, so it is the one piece of login copy the SPA renders verbatim rather
41
+ * than through the catalog.
42
+ */
43
+ const sso = ref<SsoConfigView | null>(null)
44
+ /**
45
+ * Why the last enterprise-SSO sign-in produced no session, when one was refused. Captured
46
+ * from the `#sso_error=` fragment on boot so the login screen can name the rule that refused
47
+ * instead of returning the user to an unchanged sign-in button.
48
+ */
49
+ const ssoError = ref<SsoLoginFailure | null>(null)
35
50
  /**
36
51
  * Source-control providers a HOSTED facade (remote node) accepts a user-supplied PAT for.
37
52
  * Drives the login screen's "sign in with a PAT" option on non-local deployments. Empty on
@@ -134,14 +149,29 @@ export const useAuthStore = defineStore(
134
149
  user,
135
150
  autoLoginProvider,
136
151
  })
137
- const { consumeRedirectToken, maybeConnectMothership, signInViaMothership, maybeAcceptInvite } =
138
- createAuthRedirectActions({ api, token, localMode, mothershipError, applySession })
152
+ const {
153
+ consumeRedirectToken,
154
+ consumeSsoError,
155
+ maybeConnectMothership,
156
+ signInViaMothership,
157
+ maybeAcceptInvite,
158
+ } = createAuthRedirectActions({
159
+ api,
160
+ token,
161
+ localMode,
162
+ mothershipError,
163
+ ssoError,
164
+ applySession,
165
+ })
139
166
 
140
167
  /** Resolve auth state: capture any redirect token, then check the backend. */
141
168
  async function bootstrap() {
142
169
  // A returning mothership-connect redirect is handled first (it carries a mothership session,
143
170
  // which must be exchanged — not stored as a local token by `consumeRedirectToken`).
144
171
  if (!(await maybeConnectMothership())) consumeRedirectToken()
172
+ // A refused SSO round-trip returns a reason instead of a token, and it must be read BEFORE
173
+ // the config call: the login screen renders from the same settled state either way.
174
+ consumeSsoError()
145
175
  try {
146
176
  // Tolerate a cold-start race: when the SPA and backend boot together, this first call
147
177
  // can beat the backend's listener by a second or two. Retry a not-listening-yet socket
@@ -149,6 +179,7 @@ export const useAuthStore = defineStore(
149
179
  const config = await retryWhileBackendUnreachable(() => api.getAuthConfig())
150
180
  required.value = config.enabled
151
181
  if (config.providers) providers.value = config.providers
182
+ sso.value = config.sso ?? null
152
183
  patProviders.value = config.patLogin?.providers ?? []
153
184
  testingNoAuth.value = config.testingNoAuth ?? false
154
185
  localMode.value = config.localMode ?? null
@@ -211,6 +242,8 @@ export const useAuthStore = defineStore(
211
242
  user,
212
243
  required,
213
244
  providers,
245
+ sso,
246
+ ssoError,
214
247
  patProviders,
215
248
  testingNoAuth,
216
249
  localMode,
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { SSO_ERROR_REASONS } from '@cat-factory/contracts'
3
+ import en from '../../i18n/locales/en.json'
4
+ import { SSO_ERROR_MESSAGE_KEYS } from './sso'
5
+
6
+ // The copy side of the SSO refusal contract. The `Record<SsoLoginFailure, string>` type already
7
+ // forces every reason to name a key, but a key is only a STRING: nothing there checks it exists in
8
+ // the catalog, and a typo renders the raw key path to a user who just failed to sign in. That is
9
+ // the assertion the type and the locale-parity guard structurally cannot make between them (parity
10
+ // compares locales to each other, so a key missing from ALL of them is parity-clean).
11
+
12
+ /** Resolve a dotted i18n key against the catalog, or undefined when it names nothing. */
13
+ function lookup(key: string): unknown {
14
+ return key
15
+ .split('.')
16
+ .reduce<unknown>(
17
+ (node, part) =>
18
+ node && typeof node === 'object' ? (node as Record<string, unknown>)[part] : undefined,
19
+ en,
20
+ )
21
+ }
22
+
23
+ describe('SSO_ERROR_MESSAGE_KEYS', () => {
24
+ it('covers every wire reason EXACTLY once, plus the newer-backend fallback', () => {
25
+ // Derived from the vocabulary the backend actually ships rather than a pinned count, so a
26
+ // reason added there fails here until it has wording instead of silently rendering nothing.
27
+ expect(Object.keys(SSO_ERROR_MESSAGE_KEYS).sort()).toEqual(
28
+ [...SSO_ERROR_REASONS, 'unknown'].sort(),
29
+ )
30
+ })
31
+
32
+ it('names a key that resolves to real copy for every reason', () => {
33
+ for (const [reason, key] of Object.entries(SSO_ERROR_MESSAGE_KEYS)) {
34
+ const copy = lookup(key)
35
+ expect(typeof copy, `${reason} -> ${key}`).toBe('string')
36
+ expect(copy as string, `${reason} -> ${key}`).not.toBe('')
37
+ }
38
+ })
39
+ })
@@ -0,0 +1,41 @@
1
+ import { type SsoErrorReason } from '@cat-factory/contracts'
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Enterprise SSO presentation, in ONE place — the same convention as `utils/vcs.ts`.
5
+ //
6
+ // The backend does not localize prose (CLAUDE.md's i18n rule): a refused SSO round-trip lands
7
+ // back here with a machine-readable reason, and this module is where each reason becomes copy.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ /**
11
+ * A failed SSO sign-in as the SPA models it: one of the wire reasons, or `unknown`.
12
+ *
13
+ * `unknown` is not a wire value — it is what a reason from a NEWER backend than this build reads
14
+ * as. Without it the alternatives are rendering the raw wire token to a user or showing nothing
15
+ * at all after a failed sign-in, and the second is the worse one: the user clicked the button and
16
+ * came back to the same button.
17
+ */
18
+ export type SsoLoginFailure = SsoErrorReason | 'unknown'
19
+
20
+ /**
21
+ * The copy key per failure. An exhaustive `Record`, so a member added to the wire vocabulary
22
+ * fails this typecheck until it has wording — the drift guard the `UNAVAILABLE_REASONS` pattern
23
+ * establishes.
24
+ *
25
+ * The wording split matters more than it looks: `group_required` and `domain_not_allowed` are
26
+ * things the USER takes to their IT team, while `exchange_failed` and `token_invalid` are
27
+ * OPERATOR faults in the deployment's own configuration. One "sign-in failed" for all four sends
28
+ * every user to the wrong place.
29
+ */
30
+ export const SSO_ERROR_MESSAGE_KEYS: Record<SsoLoginFailure, string> = {
31
+ state_invalid: 'auth.sso.errors.stateInvalid',
32
+ provider_denied: 'auth.sso.errors.providerDenied',
33
+ exchange_failed: 'auth.sso.errors.exchangeFailed',
34
+ token_invalid: 'auth.sso.errors.tokenInvalid',
35
+ subject_missing: 'auth.sso.errors.subjectMissing',
36
+ group_required: 'auth.sso.errors.groupRequired',
37
+ domain_not_allowed: 'auth.sso.errors.domainNotAllowed',
38
+ email_required: 'auth.sso.errors.emailRequired',
39
+ provider_unreachable: 'auth.sso.errors.providerUnreachable',
40
+ unknown: 'auth.sso.errors.unknown',
41
+ }
@@ -3644,7 +3644,9 @@
3644
3644
  "continueWithConfigured": "Mit konfiguriertem {provider}-PAT anmelden",
3645
3645
  "noPatTitle": "Kein Versionsverwaltungs-Token konfiguriert",
3646
3646
  "noPatBody": "Setzen Sie GITHUB_PAT oder GITLAB_PAT in Ihrer .env, um sich mit einem Personal Access Token anzumelden, und starten Sie dann den Server neu.",
3647
+ "setupBody": "Erstellen Sie einen Personal Access Token und fügen Sie ihn unten ein. Er meldet Sie an und wird zum Token, mit dem diese Installation klont, pusht und merged.",
3647
3648
  "createToken": "Einen {provider}-Token erstellen ↗",
3649
+ "tokenBecomesCredential": "Dieser Token wird auf diesem Rechner gespeichert und für jeden Clone, Push, PR und Merge der Agenten verwendet.",
3648
3650
  "orDivider": "oder",
3649
3651
  "failed": "Anmeldung fehlgeschlagen. Prüfen Sie, ob der konfigurierte Token gültig ist, und versuchen Sie es erneut."
3650
3652
  },
@@ -3675,10 +3677,26 @@
3675
3677
  "signInFailed": "Anmeldung fehlgeschlagen. Prüfen Sie Ihre Angaben und versuchen Sie es erneut.",
3676
3678
  "genericError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.",
3677
3679
  "notConfiguredTitle": "Authentifizierung ist nicht konfiguriert",
3678
- "notConfiguredBody": "Für dieses Deployment ist keine Anmeldemethode aktiviert, daher können Sie sich nicht anmelden oder auf Ihre Workspaces zugreifen. Ein Administrator muss einen Authentifizierungs-Provider konfigurieren (GitHub- oder Google-OAuth oder E-Mail-/Passwort-Anmeldung).",
3680
+ "notConfiguredBody": "Für dieses Deployment ist keine Anmeldemethode aktiviert, daher können Sie sich nicht anmelden oder auf Ihre Workspaces zugreifen. Ein Administrator muss einen Authentifizierungs-Provider konfigurieren (Single Sign-on über den Identitätsprovider Ihrer Organisation, GitHub- oder Google-OAuth oder E-Mail-/Passwort-Anmeldung).",
3679
3681
  "patPlaceholder": "{provider} Personal Access Token",
3680
3682
  "signInWithPat": "Mit {provider}-PAT anmelden"
3681
3683
  },
3684
+ "sso": {
3685
+ "continueWith": "Mit {provider} fortfahren",
3686
+ "failedTitle": "Single Sign-on wurde nicht abgeschlossen",
3687
+ "errors": {
3688
+ "stateInvalid": "Dieser Anmeldeversuch ist abgelaufen oder wurde bereits verwendet. Beginnen Sie erneut auf dieser Seite.",
3689
+ "providerDenied": "Ihr Identitätsprovider hat die Anmeldung abgelehnt. Falls Sie sie nicht selbst abgebrochen haben, fragen Sie Ihre IT-Abteilung, ob diese Anwendung Ihnen zugewiesen ist.",
3690
+ "exchangeFailed": "Dieses Deployment konnte den Austausch mit Ihrem Identitätsprovider nicht abschließen. Ein Administrator muss das Single-Sign-on-Client-Secret und die Redirect-URL prüfen.",
3691
+ "tokenInvalid": "Die Antwort Ihres Identitätsproviders konnte nicht verifiziert werden. Ein Administrator muss die Single-Sign-on-Konfiguration dieses Deployments prüfen.",
3692
+ "subjectMissing": "Ihr Identitätsprovider hat keine Benutzerkennung zurückgegeben, daher konnte kein Konto ermittelt werden. Ein Administrator muss prüfen, welche Claims freigegeben werden.",
3693
+ "groupRequired": "Die Anmeldung hat funktioniert, aber Sie sind in keiner Verzeichnisgruppe, die dieses Deployment nutzen darf. Bitten Sie Ihre IT-Abteilung, Sie hinzuzufügen.",
3694
+ "domainNotAllowed": "Ihre E-Mail-Domain darf sich bei diesem Deployment nicht anmelden. Fragen Sie Ihre IT-Abteilung, welches Konto Sie verwenden sollen.",
3695
+ "emailRequired": "Dieses Deployment beschränkt die Anmeldung auf bestimmte E-Mail-Domains, aber Ihr Identitätsprovider hat keine verifizierte E-Mail-Adresse freigegeben. Ein Administrator muss den E-Mail-Claim aktivieren.",
3696
+ "providerUnreachable": "Ihr Identitätsprovider hat beim Abschluss der Anmeldung nicht geantwortet, möglicherweise ist er oder die Verbindung zu ihm gestört. Versuchen Sie es in einem Moment erneut und informieren Sie einen Administrator, falls es weiterhin auftritt.",
3697
+ "unknown": "Single Sign-on ist aus einem Grund fehlgeschlagen, den diese Version nicht kennt. Versuchen Sie es erneut und informieren Sie einen Administrator, falls es weiterhin auftritt."
3698
+ }
3699
+ },
3682
3700
  "resetPassword": {
3683
3701
  "title": "Passwort zurücksetzen",
3684
3702
  "subtitle": "Wählen Sie ein neues Passwort für Ihren Account.",
@@ -5551,6 +5569,7 @@
5551
5569
  "threshold": "Schwellenwert {threshold}",
5552
5570
  "thresholdHint": "Der Wert, den dieser Schritt erreichen musste, aus der Merge-Policy der Aufgabe. Darunter schickt der Judge die Arbeit mit seinen Befunden als Nacharbeit an den erzeugenden Schritt zurück oder parkt den Lauf für Sie, wenn kein Versuchsbudget mehr übrig ist.",
5553
5571
  "rubricOverridden": "Raster des Arbeitsbereichs",
5572
+ "modelPinUnavailable": "Diese Prüfung wurde für das Modell {model} geschrieben, das diese Installation nicht ausführen kann. Die Arbeit wurde daher von einem anderen Modell bewertet.",
5554
5573
  "reworkRounds": "Überarbeitung {spent}/{budget}",
5555
5574
  "findingsHeading": "Was das Bewertungsraster beanstandet hat",
5556
5575
  "roundsHeading": "Prüfrunden",
@@ -1884,7 +1884,9 @@
1884
1884
  "continueWithConfigured": "Sign in with configured {provider} PAT",
1885
1885
  "noPatTitle": "No source-control token configured",
1886
1886
  "noPatBody": "Set GITHUB_PAT or GITLAB_PAT in your .env to sign in with a personal access token, then restart the server.",
1887
+ "setupBody": "Create a personal access token and paste it below. It signs you in and becomes the token this deployment clones, pushes and merges with.",
1887
1888
  "createToken": "Create a {provider} token ↗",
1889
+ "tokenBecomesCredential": "This token is stored on this machine and used for every clone, push, PR and merge the agents make.",
1888
1890
  "orDivider": "or",
1889
1891
  "failed": "Sign-in failed. Check that the configured token is valid and try again."
1890
1892
  },
@@ -1918,10 +1920,32 @@
1918
1920
  "signInFailed": "Sign-in failed. Check your details and try again.",
1919
1921
  "genericError": "Something went wrong. Please try again.",
1920
1922
  "notConfiguredTitle": "Authentication isn't configured",
1921
- "notConfiguredBody": "This deployment has no sign-in method enabled, so you can't sign in or access your workspaces. An administrator needs to configure an authentication provider (GitHub or Google OAuth, or email and password login).",
1923
+ "notConfiguredBody": "This deployment has no sign-in method enabled, so you can't sign in or access your workspaces. An administrator needs to configure an authentication provider (single sign-on through your organization's identity provider, GitHub or Google OAuth, or email and password login).",
1922
1924
  "patPlaceholder": "{provider} personal access token",
1923
1925
  "signInWithPat": "Sign in with {provider} PAT"
1924
1926
  },
1927
+ "sso": {
1928
+ "continueWith": "Continue with {provider}",
1929
+ "@continueWith": {
1930
+ "description": "Sign-in button for the deployment's own identity provider (enterprise SSO). {provider} is the operator-configured label (AUTH_SSO_LABEL) naming their IdP, e.g. \"Acme SSO\" or \"Okta\" - it is a proper noun supplied at runtime, so never translate the interpolated value."
1931
+ },
1932
+ "failedTitle": "Single sign-on didn't complete",
1933
+ "@failedTitle": {
1934
+ "description": "Title of the alert shown when an enterprise single-sign-on round-trip came back without a session. The per-reason detail underneath is the matching `errors.*` message."
1935
+ },
1936
+ "errors": {
1937
+ "stateInvalid": "That sign-in attempt has expired or was already used. Start again from this page.",
1938
+ "providerDenied": "Your identity provider refused the sign-in. If you didn't cancel it yourself, ask your IT team whether this application is assigned to you.",
1939
+ "exchangeFailed": "This deployment couldn't complete the exchange with your identity provider. An administrator needs to check its single sign-on client secret and redirect URL.",
1940
+ "tokenInvalid": "The response from your identity provider couldn't be verified. An administrator needs to check this deployment's single sign-on configuration.",
1941
+ "subjectMissing": "Your identity provider didn't return a user identifier, so no account could be resolved. An administrator needs to check which claims it releases.",
1942
+ "groupRequired": "Your sign-in worked, but you aren't in a directory group that's allowed to use this deployment. Ask your IT team to add you.",
1943
+ "domainNotAllowed": "Your email domain isn't allowed to sign in to this deployment. Ask your IT team which account to use.",
1944
+ "emailRequired": "This deployment restricts sign-in by email domain, but your identity provider didn't release a verified email address. An administrator needs to enable the email claim.",
1945
+ "providerUnreachable": "Your identity provider didn't respond while your sign-in was being completed, so it or the network to it may be down. Try again in a moment, and tell an administrator if it keeps happening.",
1946
+ "unknown": "Single sign-on failed for a reason this version doesn't recognise. Try again, and tell an administrator if it keeps happening."
1947
+ }
1948
+ },
1925
1949
  "resetPassword": {
1926
1950
  "title": "Reset password",
1927
1951
  "subtitle": "Choose a new password for your account.",
@@ -4910,6 +4934,7 @@
4910
4934
  "threshold": "threshold {threshold}",
4911
4935
  "thresholdHint": "The score this step had to reach, taken from the task's merge policy. Below it the judge sends the work back to the step that produced it, with its findings as rework, or parks the run for you when no attempt budget is left.",
4912
4936
  "rubricOverridden": "workspace rubric",
4937
+ "modelPinUnavailable": "This review was written for the {model} model, which this deployment cannot run, so another model scored the work.",
4913
4938
  "reworkRounds": "rework {spent}/{budget}",
4914
4939
  "findingsHeading": "What the rubric flagged",
4915
4940
  "roundsHeading": "Review rounds",
@@ -1789,7 +1789,9 @@
1789
1789
  "continueWithConfigured": "Inicia sesión con el PAT de {provider} configurado",
1790
1790
  "noPatTitle": "No hay ningún token de control de versiones configurado",
1791
1791
  "noPatBody": "Define GITHUB_PAT o GITLAB_PAT en tu .env para iniciar sesión con un token de acceso personal y reinicia el servidor.",
1792
+ "setupBody": "Crea un token de acceso personal y pégalo abajo. Inicia tu sesión y pasa a ser el token con el que esta instalación clona, publica y fusiona.",
1792
1793
  "createToken": "Crear un token de {provider} ↗",
1794
+ "tokenBecomesCredential": "Este token se guarda en esta máquina y se usa en cada clonado, push, PR y fusión que hacen los agentes.",
1793
1795
  "orDivider": "o",
1794
1796
  "failed": "Error al iniciar sesión. Comprueba que el token configurado sea válido e inténtalo de nuevo."
1795
1797
  },
@@ -1820,10 +1822,26 @@
1820
1822
  "signInFailed": "Error al iniciar sesión. Revisa tus datos e inténtalo de nuevo.",
1821
1823
  "genericError": "Algo salió mal. Inténtalo de nuevo.",
1822
1824
  "notConfiguredTitle": "La autenticación no está configurada",
1823
- "notConfiguredBody": "Este despliegue no tiene ningún método de inicio de sesión habilitado, por lo que no puedes iniciar sesión ni acceder a tus espacios de trabajo. Un administrador debe configurar un proveedor de autenticación (OAuth de GitHub o Google, o inicio de sesión con correo y contraseña).",
1825
+ "notConfiguredBody": "Este despliegue no tiene ningún método de inicio de sesión habilitado, por lo que no puedes iniciar sesión ni acceder a tus espacios de trabajo. Un administrador debe configurar un proveedor de autenticación (inicio de sesión único mediante el proveedor de identidad de tu organización, OAuth de GitHub o Google, o inicio de sesión con correo y contraseña).",
1824
1826
  "patPlaceholder": "Token de acceso personal de {provider}",
1825
1827
  "signInWithPat": "Iniciar sesión con un PAT de {provider}"
1826
1828
  },
1829
+ "sso": {
1830
+ "continueWith": "Continuar con {provider}",
1831
+ "failedTitle": "El inicio de sesión único no se completó",
1832
+ "errors": {
1833
+ "stateInvalid": "Ese intento de inicio de sesión ha caducado o ya se usó. Vuelve a empezar desde esta página.",
1834
+ "providerDenied": "Tu proveedor de identidad rechazó el inicio de sesión. Si no lo cancelaste tú, pregunta a tu equipo de TI si esta aplicación está asignada a ti.",
1835
+ "exchangeFailed": "Este despliegue no pudo completar el intercambio con tu proveedor de identidad. Un administrador debe revisar el secreto de cliente y la URL de redirección del inicio de sesión único.",
1836
+ "tokenInvalid": "No se pudo verificar la respuesta de tu proveedor de identidad. Un administrador debe revisar la configuración de inicio de sesión único de este despliegue.",
1837
+ "subjectMissing": "Tu proveedor de identidad no devolvió un identificador de usuario, por lo que no se pudo resolver ninguna cuenta. Un administrador debe revisar qué claims publica.",
1838
+ "groupRequired": "Te has autenticado correctamente, pero no perteneces a ningún grupo del directorio autorizado a usar este despliegue. Pide a tu equipo de TI que te añada.",
1839
+ "domainNotAllowed": "Tu dominio de correo no tiene permitido iniciar sesión en este despliegue. Pregunta a tu equipo de TI qué cuenta debes usar.",
1840
+ "emailRequired": "Este despliegue restringe el inicio de sesión por dominio de correo, pero tu proveedor de identidad no publicó una dirección de correo verificada. Un administrador debe habilitar el claim de correo.",
1841
+ "providerUnreachable": "Tu proveedor de identidad no respondió mientras se completaba el inicio de sesión, por lo que puede estar caído o sin conexión. Vuelve a intentarlo en un momento y avisa a un administrador si sigue ocurriendo.",
1842
+ "unknown": "El inicio de sesión único falló por un motivo que esta versión no reconoce. Inténtalo de nuevo y avisa a un administrador si sigue ocurriendo."
1843
+ }
1844
+ },
1827
1845
  "resetPassword": {
1828
1846
  "title": "Restablecer contraseña",
1829
1847
  "subtitle": "Elige una nueva contraseña para tu cuenta.",
@@ -4693,6 +4711,7 @@
4693
4711
  "threshold": "umbral {threshold}",
4694
4712
  "thresholdHint": "La puntuacion que este paso tenia que alcanzar, tomada de la politica de fusion de la tarea. Por debajo, el juez devuelve el trabajo al paso que lo produjo con sus hallazgos como retrabajo, o aparca la ejecucion para ti cuando ya no queda presupuesto de intentos.",
4695
4713
  "rubricOverridden": "rúbrica del espacio de trabajo",
4714
+ "modelPinUnavailable": "Esta revisión se escribió para el modelo {model}, que esta instalación no puede ejecutar, así que otro modelo puntuó el trabajo.",
4696
4715
  "reworkRounds": "revisión {spent}/{budget}",
4697
4716
  "findingsHeading": "Lo que señaló la rúbrica",
4698
4717
  "roundsHeading": "Rondas de revisión",
@@ -1789,7 +1789,9 @@
1789
1789
  "continueWithConfigured": "Se connecter avec le PAT {provider} configuré",
1790
1790
  "noPatTitle": "Aucun jeton de gestion de versions configuré",
1791
1791
  "noPatBody": "Définissez GITHUB_PAT ou GITLAB_PAT dans votre .env pour vous connecter avec un jeton d'accès personnel, puis redémarrez le serveur.",
1792
+ "setupBody": "Créez un jeton d'accès personnel et collez-le ci-dessous. Il vous connecte et devient le jeton avec lequel cette installation clone, pousse et fusionne.",
1792
1793
  "createToken": "Créer un jeton {provider} ↗",
1794
+ "tokenBecomesCredential": "Ce jeton est stocké sur cette machine et utilisé pour chaque clone, push, PR et fusion des agents.",
1793
1795
  "orDivider": "ou",
1794
1796
  "failed": "Échec de la connexion. Vérifiez que le jeton configuré est valide et réessayez."
1795
1797
  },
@@ -1820,10 +1822,26 @@
1820
1822
  "signInFailed": "Échec de la connexion. Vérifiez vos informations et réessayez.",
1821
1823
  "genericError": "Une erreur s'est produite. Veuillez réessayer.",
1822
1824
  "notConfiguredTitle": "L'authentification n'est pas configurée",
1823
- "notConfiguredBody": "Ce déploiement n'a aucune méthode de connexion activée, vous ne pouvez donc pas vous connecter ni accéder à vos espaces de travail. Un administrateur doit configurer un fournisseur d'authentification (OAuth GitHub ou Google, ou connexion par e-mail et mot de passe).",
1825
+ "notConfiguredBody": "Ce déploiement n'a aucune méthode de connexion activée, vous ne pouvez donc pas vous connecter ni accéder à vos espaces de travail. Un administrateur doit configurer un fournisseur d'authentification (authentification unique via le fournisseur d'identité de votre organisation, OAuth GitHub ou Google, ou connexion par e-mail et mot de passe).",
1824
1826
  "patPlaceholder": "Jeton d'accès personnel {provider}",
1825
1827
  "signInWithPat": "Se connecter avec un PAT {provider}"
1826
1828
  },
1829
+ "sso": {
1830
+ "continueWith": "Continuer avec {provider}",
1831
+ "failedTitle": "L'authentification unique n'a pas abouti",
1832
+ "errors": {
1833
+ "stateInvalid": "Cette tentative de connexion a expiré ou a déjà été utilisée. Recommencez depuis cette page.",
1834
+ "providerDenied": "Votre fournisseur d'identité a refusé la connexion. Si vous ne l'avez pas annulée vous-même, demandez à votre service informatique si cette application vous est attribuée.",
1835
+ "exchangeFailed": "Ce déploiement n'a pas pu finaliser l'échange avec votre fournisseur d'identité. Un administrateur doit vérifier le secret client et l'URL de redirection de l'authentification unique.",
1836
+ "tokenInvalid": "La réponse de votre fournisseur d'identité n'a pas pu être vérifiée. Un administrateur doit vérifier la configuration de l'authentification unique de ce déploiement.",
1837
+ "subjectMissing": "Votre fournisseur d'identité n'a pas renvoyé d'identifiant utilisateur, aucun compte n'a donc pu être résolu. Un administrateur doit vérifier les claims qu'il expose.",
1838
+ "groupRequired": "Votre connexion a réussi, mais vous n'appartenez à aucun groupe de l'annuaire autorisé à utiliser ce déploiement. Demandez à votre service informatique de vous ajouter.",
1839
+ "domainNotAllowed": "Votre domaine de messagerie n'est pas autorisé à se connecter à ce déploiement. Demandez à votre service informatique quel compte utiliser.",
1840
+ "emailRequired": "Ce déploiement limite la connexion à certains domaines de messagerie, mais votre fournisseur d'identité n'a pas transmis d'adresse e-mail vérifiée. Un administrateur doit activer le claim e-mail.",
1841
+ "providerUnreachable": "Votre fournisseur d'identité n'a pas répondu pendant la finalisation de la connexion : lui-même ou le réseau qui y mène est peut-être indisponible. Réessayez dans un instant et prévenez un administrateur si cela persiste.",
1842
+ "unknown": "L'authentification unique a échoué pour une raison que cette version ne reconnaît pas. Réessayez et prévenez un administrateur si le problème persiste."
1843
+ }
1844
+ },
1827
1845
  "resetPassword": {
1828
1846
  "title": "Réinitialiser le mot de passe",
1829
1847
  "subtitle": "Choisissez un nouveau mot de passe pour votre compte.",
@@ -4693,6 +4711,7 @@
4693
4711
  "threshold": "seuil {threshold}",
4694
4712
  "thresholdHint": "Le score que cette etape devait atteindre, issu de la politique de fusion de la tache. En dessous, le juge renvoie le travail a l'etape qui l'a produit avec ses constats a reprendre, ou met l'execution en attente pour vous quand il ne reste plus de budget de tentatives.",
4695
4713
  "rubricOverridden": "grille de l'espace de travail",
4714
+ "modelPinUnavailable": "Cette revue a été écrite pour le modèle {model}, que ce déploiement ne peut pas exécuter : un autre modèle a donc noté le travail.",
4696
4715
  "reworkRounds": "reprise {spent}/{budget}",
4697
4716
  "findingsHeading": "Ce que la grille a signalé",
4698
4717
  "roundsHeading": "Tours de revue",
@@ -1789,7 +1789,9 @@
1789
1789
  "continueWithConfigured": "התחבר באמצעות PAT מוגדר של {provider}",
1790
1790
  "noPatTitle": "לא הוגדר טוקן לבקרת מקור",
1791
1791
  "noPatBody": "הגדר את GITHUB_PAT או GITLAB_PAT בקובץ ה-.env שלך כדי להתחבר עם טוקן גישה אישי, ואז הפעל מחדש את השרת.",
1792
+ "setupBody": "צור טוקן גישה אישי והדבק אותו למטה. הוא מחבר אותך וגם הופך לטוקן שבו ההתקנה הזו משכפלת, דוחפת וממזגת.",
1792
1793
  "createToken": "צור טוקן {provider} ↗",
1794
+ "tokenBecomesCredential": "הטוקן נשמר במחשב הזה ומשמש לכל שכפול, דחיפה, PR ומיזוג שהסוכנים מבצעים.",
1793
1795
  "orDivider": "או",
1794
1796
  "failed": "ההתחברות נכשלה. ודא שהטוקן המוגדר תקף ונסה שוב."
1795
1797
  },
@@ -1820,10 +1822,26 @@
1820
1822
  "signInFailed": "ההתחברות נכשלה. בדוק את הפרטים שלך ונסה שוב.",
1821
1823
  "genericError": "משהו השתבש. אנא נסה שוב.",
1822
1824
  "notConfiguredTitle": "האימות אינו מוגדר",
1823
- "notConfiguredBody": "בפריסה זו לא מופעלת אף שיטת התחברות, ולכן לא ניתן להתחבר או לגשת למרחבי העבודה שלך. מנהל המערכת צריך להגדיר ספק אימות (GitHub או Google OAuth, או התחברות עם אימייל וסיסמה).",
1825
+ "notConfiguredBody": "בפריסה זו לא מופעלת אף שיטת התחברות, ולכן לא ניתן להתחבר או לגשת למרחבי העבודה שלך. מנהל המערכת צריך להגדיר ספק אימות (התחברות מאוחדת דרך ספק הזהויות של הארגון שלכם, GitHub או Google OAuth, או התחברות עם אימייל וסיסמה).",
1824
1826
  "patPlaceholder": "אסימון גישה אישי של {provider}",
1825
1827
  "signInWithPat": "התחברות עם PAT של {provider}"
1826
1828
  },
1829
+ "sso": {
1830
+ "continueWith": "המשך עם {provider}",
1831
+ "failedTitle": "ההתחברות המאוחדת לא הושלמה",
1832
+ "errors": {
1833
+ "stateInvalid": "ניסיון ההתחברות הזה פג או שכבר נעשה בו שימוש. התחילו מחדש מדף זה.",
1834
+ "providerDenied": "ספק הזהויות שלכם דחה את ההתחברות. אם לא ביטלתם אותה בעצמכם, בדקו עם צוות ה-IT אם היישום הזה מוקצה לכם.",
1835
+ "exchangeFailed": "הפריסה הזו לא הצליחה להשלים את ההחלפה עם ספק הזהויות שלכם. מנהל המערכת צריך לבדוק את סוד הלקוח ואת כתובת ההפניה של ההתחברות המאוחדת.",
1836
+ "tokenInvalid": "לא ניתן היה לאמת את התשובה מספק הזהויות שלכם. מנהל המערכת צריך לבדוק את הגדרות ההתחברות המאוחדת של הפריסה.",
1837
+ "subjectMissing": "ספק הזהויות לא החזיר מזהה משתמש, ולכן לא ניתן היה לאתר חשבון. מנהל המערכת צריך לבדוק אילו claims הוא חושף.",
1838
+ "groupRequired": "ההתחברות הצליחה, אך אינכם חברים בקבוצת ספרייה שמורשית להשתמש בפריסה הזו. בקשו מצוות ה-IT להוסיף אתכם.",
1839
+ "domainNotAllowed": "הדומיין של האימייל שלכם אינו מורשה להתחבר לפריסה הזו. בדקו עם צוות ה-IT באיזה חשבון להשתמש.",
1840
+ "emailRequired": "הפריסה הזו מגבילה התחברות לפי דומיין אימייל, אך ספק הזהויות לא חשף כתובת אימייל מאומתת. מנהל המערכת צריך להפעיל את claim האימייל.",
1841
+ "providerUnreachable": "ספק הזהויות שלך לא הגיב בעת השלמת ההתחברות, ולכן ייתכן שהוא או הרשת אליו אינם זמינים. נסו שוב בעוד רגע, ואם התקלה חוזרת דווחו למנהל המערכת.",
1842
+ "unknown": "ההתחברות המאוחדת נכשלה מסיבה שגרסה זו אינה מזהה. נסו שוב, ואם התקלה חוזרת עדכנו את מנהל המערכת."
1843
+ }
1844
+ },
1827
1845
  "resetPassword": {
1828
1846
  "title": "אפס סיסמה",
1829
1847
  "subtitle": "בחר סיסמה חדשה לחשבונך.",
@@ -4693,6 +4711,7 @@
4693
4711
  "threshold": "סף {threshold}",
4694
4712
  "thresholdHint": "הציון שהשלב הזה היה צריך להגיע אליו, לפי מדיניות המיזוג של המשימה. מתחת לכך השופט מחזיר את העבודה לשלב שיצר אותה, עם הממצאים לתיקון, או משהה עבורכם את ההרצה כשלא נותר תקציב ניסיונות.",
4695
4713
  "rubricOverridden": "מחוון סביבת העבודה",
4714
+ "modelPinUnavailable": "הביקורת הזו נכתבה עבור המודל {model}, שהפריסה הזו לא יכולה להריץ, ולכן מודל אחר ניקד את העבודה.",
4696
4715
  "reworkRounds": "עיבוד מחדש {spent}/{budget}",
4697
4716
  "findingsHeading": "מה שהמחוון סימן",
4698
4717
  "roundsHeading": "סבבי בדיקה",
@@ -3644,7 +3644,9 @@
3644
3644
  "continueWithConfigured": "Accedi con il PAT {provider} configurato",
3645
3645
  "noPatTitle": "Nessun token di controllo del codice sorgente configurato",
3646
3646
  "noPatBody": "Imposta GITHUB_PAT o GITLAB_PAT nel tuo file .env per accedere con un personal access token, poi riavvia il server.",
3647
+ "setupBody": "Crea un personal access token e incollalo qui sotto. Ti autentica e diventa il token con cui questa installazione clona, pubblica e unisce.",
3647
3648
  "createToken": "Crea un token {provider} ↗",
3649
+ "tokenBecomesCredential": "Questo token viene salvato su questa macchina e usato per ogni clone, push, PR e merge degli agenti.",
3648
3650
  "orDivider": "oppure",
3649
3651
  "failed": "Accesso non riuscito. Verifica che il token configurato sia valido e riprova."
3650
3652
  },
@@ -3675,10 +3677,26 @@
3675
3677
  "signInFailed": "Accesso non riuscito. Controlla i tuoi dati e riprova.",
3676
3678
  "genericError": "Qualcosa è andato storto. Riprova.",
3677
3679
  "notConfiguredTitle": "L'autenticazione non è configurata",
3678
- "notConfiguredBody": "Questo deployment non ha alcun metodo di accesso abilitato, quindi non puoi accedere o utilizzare i tuoi workspace. Un amministratore deve configurare un provider di autenticazione (OAuth GitHub o Google, oppure accesso con email e password).",
3680
+ "notConfiguredBody": "Questo deployment non ha alcun metodo di accesso abilitato, quindi non puoi accedere o utilizzare i tuoi workspace. Un amministratore deve configurare un provider di autenticazione (accesso unico tramite il provider di identità della tua organizzazione, OAuth GitHub o Google, oppure accesso con email e password).",
3679
3681
  "patPlaceholder": "personal access token {provider}",
3680
3682
  "signInWithPat": "Accedi con il PAT {provider}"
3681
3683
  },
3684
+ "sso": {
3685
+ "continueWith": "Continua con {provider}",
3686
+ "failedTitle": "L'accesso unico non è stato completato",
3687
+ "errors": {
3688
+ "stateInvalid": "Questo tentativo di accesso è scaduto o è già stato usato. Riprova da questa pagina.",
3689
+ "providerDenied": "Il tuo provider di identità ha rifiutato l'accesso. Se non l'hai annullato tu, chiedi al team IT se questa applicazione ti è stata assegnata.",
3690
+ "exchangeFailed": "Questo deployment non ha potuto completare lo scambio con il tuo provider di identità. Un amministratore deve verificare il client secret e l'URL di reindirizzamento dell'accesso unico.",
3691
+ "tokenInvalid": "Non è stato possibile verificare la risposta del tuo provider di identità. Un amministratore deve controllare la configurazione dell'accesso unico di questo deployment.",
3692
+ "subjectMissing": "Il tuo provider di identità non ha restituito un identificatore utente, quindi non è stato possibile risolvere alcun account. Un amministratore deve controllare quali claim vengono esposti.",
3693
+ "groupRequired": "L'accesso è riuscito, ma non appartieni a nessun gruppo della directory autorizzato a usare questo deployment. Chiedi al team IT di aggiungerti.",
3694
+ "domainNotAllowed": "Il tuo dominio email non è autorizzato ad accedere a questo deployment. Chiedi al team IT quale account usare.",
3695
+ "emailRequired": "Questo deployment limita l'accesso per dominio email, ma il tuo provider di identità non ha esposto un indirizzo email verificato. Un amministratore deve abilitare il claim email.",
3696
+ "providerUnreachable": "Il tuo provider di identità non ha risposto durante il completamento dell'accesso, quindi potrebbe non essere raggiungibile. Riprova tra un momento e avvisa un amministratore se il problema persiste.",
3697
+ "unknown": "L'accesso unico è fallito per un motivo che questa versione non riconosce. Riprova e avvisa un amministratore se il problema persiste."
3698
+ }
3699
+ },
3682
3700
  "resetPassword": {
3683
3701
  "title": "Reimposta password",
3684
3702
  "subtitle": "Scegli una nuova password per il tuo account.",
@@ -5551,6 +5569,7 @@
5551
5569
  "threshold": "soglia {threshold}",
5552
5570
  "thresholdHint": "Il punteggio che questo step doveva raggiungere, preso dalla policy di merge dell'attivita. Al di sotto il giudice rimanda il lavoro allo step che lo ha prodotto, con i suoi rilievi da correggere, oppure mette in pausa l'esecuzione per te quando non resta budget di tentativi.",
5553
5571
  "rubricOverridden": "rubrica dello spazio di lavoro",
5572
+ "modelPinUnavailable": "Questa revisione è stata scritta per il modello {model}, che questa installazione non può eseguire, quindi il lavoro è stato valutato da un altro modello.",
5554
5573
  "reworkRounds": "revisione {spent}/{budget}",
5555
5574
  "findingsHeading": "Cosa ha segnalato la rubrica",
5556
5575
  "roundsHeading": "Cicli di revisione",
@@ -1789,7 +1789,9 @@
1789
1789
  "continueWithConfigured": "設定済みの {provider} PAT でサインイン",
1790
1790
  "noPatTitle": "ソース管理トークンが設定されていません",
1791
1791
  "noPatBody": ".env に GITHUB_PAT または GITLAB_PAT を設定するとパーソナルアクセストークンでサインインできます。設定後、サーバーを再起動してください。",
1792
+ "setupBody": "パーソナルアクセストークンを作成して下に貼り付けてください。サインインに使われるとともに、この環境がクローン・プッシュ・マージに使うトークンになります。",
1792
1793
  "createToken": "{provider} トークンを作成 ↗",
1794
+ "tokenBecomesCredential": "このトークンはこのマシンに保存され、エージェントによるクローン・プッシュ・PR・マージのすべてに使われます。",
1793
1795
  "orDivider": "または",
1794
1796
  "failed": "サインインに失敗しました。設定されたトークンが有効か確認して、もう一度お試しください。"
1795
1797
  },
@@ -1820,10 +1822,26 @@
1820
1822
  "signInFailed": "サインインに失敗しました。入力内容を確認して、もう一度お試しください。",
1821
1823
  "genericError": "問題が発生しました。もう一度お試しください。",
1822
1824
  "notConfiguredTitle": "認証が設定されていません",
1823
- "notConfiguredBody": "このデプロイにはサインイン方法が有効になっていないため、サインインやワークスペースへのアクセスができません。管理者が認証プロバイダー(GitHub または Google の OAuth、あるいはメールアドレスとパスワードによるログイン)を設定する必要があります。",
1825
+ "notConfiguredBody": "このデプロイにはサインイン方法が有効になっていないため、サインインやワークスペースへのアクセスができません。管理者が認証プロバイダー(組織の ID プロバイダーによるシングルサインオン、GitHub または Google の OAuth、あるいはメールアドレスとパスワードによるログイン)を設定する必要があります。",
1824
1826
  "patPlaceholder": "{provider} のパーソナルアクセストークン",
1825
1827
  "signInWithPat": "{provider} の PAT でサインイン"
1826
1828
  },
1829
+ "sso": {
1830
+ "continueWith": "{provider} で続行",
1831
+ "failedTitle": "シングルサインオンを完了できませんでした",
1832
+ "errors": {
1833
+ "stateInvalid": "このサインイン試行は期限切れか、すでに使用されています。このページからやり直してください。",
1834
+ "providerDenied": "ID プロバイダーがサインインを拒否しました。ご自身でキャンセルしていない場合は、このアプリケーションが割り当てられているか IT 部門に確認してください。",
1835
+ "exchangeFailed": "このデプロイは ID プロバイダーとの交換を完了できませんでした。管理者がシングルサインオンのクライアントシークレットとリダイレクト URL を確認する必要があります。",
1836
+ "tokenInvalid": "ID プロバイダーからの応答を検証できませんでした。管理者がこのデプロイのシングルサインオン設定を確認する必要があります。",
1837
+ "subjectMissing": "ID プロバイダーがユーザー識別子を返さなかったため、アカウントを特定できませんでした。管理者が公開しているクレームを確認する必要があります。",
1838
+ "groupRequired": "サインインは成功しましたが、このデプロイの利用を許可されたディレクトリグループに所属していません。IT 部門に追加を依頼してください。",
1839
+ "domainNotAllowed": "お使いのメールドメインはこのデプロイへのサインインを許可されていません。どのアカウントを使うべきか IT 部門に確認してください。",
1840
+ "emailRequired": "このデプロイはメールドメインでサインインを制限していますが、ID プロバイダーが検証済みのメールアドレスを公開しませんでした。管理者がメールクレームを有効にする必要があります。",
1841
+ "providerUnreachable": "サインインの完了中に ID プロバイダーから応答がありませんでした。プロバイダー自体か、そこへのネットワークが停止している可能性があります。少し待ってからもう一度お試しいただき、繰り返す場合は管理者にお知らせください。",
1842
+ "unknown": "このバージョンが認識できない理由でシングルサインオンに失敗しました。もう一度お試しいただき、繰り返す場合は管理者にご連絡ください。"
1843
+ }
1844
+ },
1827
1845
  "resetPassword": {
1828
1846
  "title": "パスワードをリセット",
1829
1847
  "subtitle": "アカウントの新しいパスワードを選択してください。",
@@ -4693,6 +4711,7 @@
4693
4711
  "threshold": "しきい値 {threshold}",
4694
4712
  "thresholdHint": "このステップが達成すべきスコアで、タスクのマージポリシーから取られます。これを下回ると、ジャッジは指摘事項を手戻りとして生成元のステップに差し戻すか、試行回数の予算が尽きている場合は実行を保留してあなたの判断を待ちます。",
4695
4713
  "rubricOverridden": "ワークスペースのルーブリック",
4714
+ "modelPinUnavailable": "このレビューは {model} モデル向けに書かれていますが、このデプロイでは実行できないため、別のモデルが作業を採点しました。",
4696
4715
  "reworkRounds": "手戻り {spent}/{budget}",
4697
4716
  "findingsHeading": "ルーブリックが指摘した点",
4698
4717
  "roundsHeading": "レビューの回数",
@@ -1789,7 +1789,9 @@
1789
1789
  "continueWithConfigured": "Zaloguj się skonfigurowanym tokenem PAT {provider}",
1790
1790
  "noPatTitle": "Nie skonfigurowano tokenu systemu kontroli wersji",
1791
1791
  "noPatBody": "Ustaw GITHUB_PAT lub GITLAB_PAT w pliku .env, aby zalogować się za pomocą osobistego tokenu dostępu, a następnie zrestartuj serwer.",
1792
+ "setupBody": "Utwórz osobisty token dostępu i wklej go poniżej. Zaloguje Cię i stanie się tokenem, którym ta instalacja klonuje, wypycha zmiany i scala.",
1792
1793
  "createToken": "Utwórz token {provider} ↗",
1794
+ "tokenBecomesCredential": "Token jest zapisywany na tym komputerze i używany przy każdym klonowaniu, wypchnięciu, PR i scaleniu wykonywanym przez agentów.",
1793
1795
  "orDivider": "lub",
1794
1796
  "failed": "Logowanie nie powiodło się. Sprawdź, czy skonfigurowany token jest prawidłowy, i spróbuj ponownie."
1795
1797
  },
@@ -1820,10 +1822,26 @@
1820
1822
  "signInFailed": "Logowanie nie powiodło się. Sprawdź swoje dane i spróbuj ponownie.",
1821
1823
  "genericError": "Coś poszło nie tak. Spróbuj ponownie.",
1822
1824
  "notConfiguredTitle": "Uwierzytelnianie nie jest skonfigurowane",
1823
- "notConfiguredBody": "To wdrożenie nie ma włączonej żadnej metody logowania, więc nie możesz się zalogować ani uzyskać dostępu do swoich przestrzeni roboczych. Administrator musi skonfigurować dostawcę uwierzytelniania (OAuth GitHub lub Google albo logowanie e-mailem i hasłem).",
1825
+ "notConfiguredBody": "To wdrożenie nie ma włączonej żadnej metody logowania, więc nie możesz się zalogować ani uzyskać dostępu do swoich przestrzeni roboczych. Administrator musi skonfigurować dostawcę uwierzytelniania (logowanie jednokrotne przez dostawcę tożsamości Twojej organizacji, OAuth GitHub lub Google albo logowanie e-mailem i hasłem).",
1824
1826
  "patPlaceholder": "Osobisty token dostępu {provider}",
1825
1827
  "signInWithPat": "Zaloguj się tokenem PAT {provider}"
1826
1828
  },
1829
+ "sso": {
1830
+ "continueWith": "Kontynuuj z {provider}",
1831
+ "failedTitle": "Nie udało się ukończyć logowania jednokrotnego",
1832
+ "errors": {
1833
+ "stateInvalid": "Ta próba logowania wygasła lub została już użyta. Zacznij ponownie z tej strony.",
1834
+ "providerDenied": "Twój dostawca tożsamości odrzucił logowanie. Jeśli nie anulowałeś go samodzielnie, zapytaj zespół IT, czy ta aplikacja jest do Ciebie przypisana.",
1835
+ "exchangeFailed": "To wdrożenie nie mogło zakończyć wymiany z dostawcą tożsamości. Administrator musi sprawdzić sekret klienta i adres przekierowania logowania jednokrotnego.",
1836
+ "tokenInvalid": "Nie udało się zweryfikować odpowiedzi dostawcy tożsamości. Administrator musi sprawdzić konfigurację logowania jednokrotnego tego wdrożenia.",
1837
+ "subjectMissing": "Dostawca tożsamości nie zwrócił identyfikatora użytkownika, więc nie udało się ustalić konta. Administrator musi sprawdzić, które oświadczenia (claims) są udostępniane.",
1838
+ "groupRequired": "Logowanie się udało, ale nie należysz do żadnej grupy katalogowej uprawnionej do korzystania z tego wdrożenia. Poproś zespół IT o dodanie Cię.",
1839
+ "domainNotAllowed": "Twoja domena e-mail nie ma uprawnień do logowania w tym wdrożeniu. Zapytaj zespół IT, którego konta użyć.",
1840
+ "emailRequired": "To wdrożenie ogranicza logowanie do wybranych domen e-mail, ale dostawca tożsamości nie udostępnił zweryfikowanego adresu e-mail. Administrator musi włączyć oświadczenie e-mail.",
1841
+ "providerUnreachable": "Twój dostawca tożsamości nie odpowiedział podczas kończenia logowania, więc on sam lub połączenie z nim może być niedostępne. Spróbuj ponownie po chwili, a jeśli problem się powtarza, powiadom administratora.",
1842
+ "unknown": "Logowanie jednokrotne nie powiodło się z powodu, którego ta wersja nie rozpoznaje. Spróbuj ponownie, a jeśli problem się powtarza, powiadom administratora."
1843
+ }
1844
+ },
1827
1845
  "resetPassword": {
1828
1846
  "title": "Zresetuj hasło",
1829
1847
  "subtitle": "Wybierz nowe hasło do swojego konta.",
@@ -4693,6 +4711,7 @@
4693
4711
  "threshold": "próg {threshold}",
4694
4712
  "thresholdHint": "Wynik, który ten krok musiał osiągnąć, wzięty z polityki scalania zadania. Poniżej sędzia odsyła pracę do kroku, który ją wytworzył, przekazując swoje uwagi do poprawy, albo wstrzymuje przebieg i czeka na Ciebie, gdy skończy się budżet prób.",
4695
4713
  "rubricOverridden": "rubryka przestrzeni roboczej",
4714
+ "modelPinUnavailable": "Ta ocena została napisana dla modelu {model}, którego to wdrożenie nie może uruchomić, więc pracę ocenił inny model.",
4696
4715
  "reworkRounds": "poprawki {spent}/{budget}",
4697
4716
  "findingsHeading": "Co zgłosiła rubryka",
4698
4717
  "roundsHeading": "Rundy przeglądu",
@@ -1789,7 +1789,9 @@
1789
1789
  "continueWithConfigured": "Yapılandırılmış {provider} PAT ile oturum aç",
1790
1790
  "noPatTitle": "Yapılandırılmış kaynak denetimi token'ı yok",
1791
1791
  "noPatBody": "Kişisel erişim token'ı ile oturum açmak için .env dosyanızda GITHUB_PAT veya GITLAB_PAT ayarlayın, ardından sunucuyu yeniden başlatın.",
1792
+ "setupBody": "Bir kişisel erişim token’ı oluşturup aşağıya yapıştırın. Hem oturumunuzu açar hem de bu kurulumun klonlama, push ve birleştirme işlemlerinde kullandığı token olur.",
1792
1793
  "createToken": "{provider} token'ı oluştur ↗",
1794
+ "tokenBecomesCredential": "Bu token bu makinede saklanır ve ajanların yaptığı her klonlama, push, PR ve birleştirmede kullanılır.",
1793
1795
  "orDivider": "veya",
1794
1796
  "failed": "Oturum açma başarısız. Yapılandırılan token'ın geçerli olduğunu kontrol edip tekrar deneyin."
1795
1797
  },
@@ -1820,10 +1822,26 @@
1820
1822
  "signInFailed": "Oturum açma başarısız. Bilgilerinizi kontrol edip tekrar deneyin.",
1821
1823
  "genericError": "Bir şeyler ters gitti. Lütfen tekrar deneyin.",
1822
1824
  "notConfiguredTitle": "Kimlik doğrulama yapılandırılmamış",
1823
- "notConfiguredBody": "Bu dağıtımda etkin bir oturum açma yöntemi yok, bu nedenle oturum açamaz veya çalışma alanlarınıza erişemezsiniz. Bir yönetici, bir kimlik doğrulama sağlayıcısı (GitHub veya Google OAuth ya da e-posta ve parola ile oturum açma) yapılandırmalıdır.",
1825
+ "notConfiguredBody": "Bu dağıtımda etkin bir oturum açma yöntemi yok, bu nedenle oturum açamaz veya çalışma alanlarınıza erişemezsiniz. Bir yönetici, bir kimlik doğrulama sağlayıcısı (kuruluşunuzun kimlik sağlayıcısı üzerinden tek oturum açma, GitHub veya Google OAuth ya da e-posta ve parola ile oturum açma) yapılandırmalıdır.",
1824
1826
  "patPlaceholder": "{provider} kişisel erişim belirteci",
1825
1827
  "signInWithPat": "{provider} PAT ile oturum aç"
1826
1828
  },
1829
+ "sso": {
1830
+ "continueWith": "{provider} ile devam et",
1831
+ "failedTitle": "Tek oturum açma tamamlanamadı",
1832
+ "errors": {
1833
+ "stateInvalid": "Bu oturum açma denemesinin süresi doldu veya daha önce kullanıldı. Bu sayfadan yeniden başlayın.",
1834
+ "providerDenied": "Kimlik sağlayıcınız oturum açmayı reddetti. İptal eden siz değilseniz, bu uygulamanın size atanıp atanmadığını BT ekibinize sorun.",
1835
+ "exchangeFailed": "Bu dağıtım, kimlik sağlayıcınızla değişimi tamamlayamadı. Bir yöneticinin tek oturum açma istemci parolasını ve yönlendirme adresini kontrol etmesi gerekiyor.",
1836
+ "tokenInvalid": "Kimlik sağlayıcınızdan gelen yanıt doğrulanamadı. Bir yöneticinin bu dağıtımın tek oturum açma yapılandırmasını kontrol etmesi gerekiyor.",
1837
+ "subjectMissing": "Kimlik sağlayıcınız bir kullanıcı tanımlayıcısı döndürmedi, bu nedenle hiçbir hesap çözümlenemedi. Bir yöneticinin hangi taleplerin (claims) paylaşıldığını kontrol etmesi gerekiyor.",
1838
+ "groupRequired": "Oturum açma başarılı oldu, ancak bu dağıtımı kullanmasına izin verilen bir dizin grubunda değilsiniz. BT ekibinizden sizi eklemesini isteyin.",
1839
+ "domainNotAllowed": "E-posta alan adınızın bu dağıtımda oturum açma izni yok. Hangi hesabı kullanmanız gerektiğini BT ekibinize sorun.",
1840
+ "emailRequired": "Bu dağıtım oturum açmayı e-posta alan adına göre kısıtlıyor, ancak kimlik sağlayıcınız doğrulanmış bir e-posta adresi paylaşmadı. Bir yöneticinin e-posta talebini etkinleştirmesi gerekiyor.",
1841
+ "providerUnreachable": "Oturum açma işlemi tamamlanırken kimlik sağlayıcınız yanıt vermedi; sağlayıcı ya da ona giden ağ çalışmıyor olabilir. Biraz sonra yeniden deneyin, sorun sürerse bir yöneticiye bildirin.",
1842
+ "unknown": "Tek oturum açma, bu sürümün tanımadığı bir nedenle başarısız oldu. Tekrar deneyin ve sorun sürerse bir yöneticiye bildirin."
1843
+ }
1844
+ },
1827
1845
  "resetPassword": {
1828
1846
  "title": "Parolayı sıfırla",
1829
1847
  "subtitle": "Hesabınız için yeni bir parola seçin.",
@@ -4693,6 +4711,7 @@
4693
4711
  "threshold": "eşik {threshold}",
4694
4712
  "thresholdHint": "Bu adımın ulaşması gereken puan; görevin birleştirme politikasından alınır. Altında kaldığında hakem, bulgularını yeniden çalışma olarak ekleyip işi üreten adıma geri gönderir; deneme bütçesi kalmadıysa çalıştırmayı sizin için beklemeye alır.",
4695
4713
  "rubricOverridden": "çalışma alanı ölçütü",
4714
+ "modelPinUnavailable": "Bu inceleme {model} modeli için yazıldı ancak bu kurulum onu çalıştıramıyor, bu yüzden işi başka bir model puanladı.",
4696
4715
  "reworkRounds": "yeniden çalışma {spent}/{budget}",
4697
4716
  "findingsHeading": "Ölçütün işaretledikleri",
4698
4717
  "roundsHeading": "İnceleme turları",
@@ -1789,7 +1789,9 @@
1789
1789
  "continueWithConfigured": "Увійти за допомогою налаштованого PAT {provider}",
1790
1790
  "noPatTitle": "Токен системи контролю версій не налаштовано",
1791
1791
  "noPatBody": "Установіть GITHUB_PAT або GITLAB_PAT у файлі .env, щоб увійти за допомогою особистого токена доступу, потім перезапустіть сервер.",
1792
+ "setupBody": "Створіть особистий токен доступу та вставте його нижче. Він виконає вхід і стане токеном, яким ця інсталяція клонує, надсилає зміни та зливає гілки.",
1792
1793
  "createToken": "Створити токен {provider} ↗",
1794
+ "tokenBecomesCredential": "Токен зберігається на цій машині й використовується для кожного клонування, надсилання, PR та злиття, які роблять агенти.",
1793
1795
  "orDivider": "або",
1794
1796
  "failed": "Не вдалося увійти. Перевірте, що налаштований токен дійсний, і спробуйте ще раз."
1795
1797
  },
@@ -1820,10 +1822,26 @@
1820
1822
  "signInFailed": "Не вдалося увійти. Перевірте свої дані та спробуйте ще раз.",
1821
1823
  "genericError": "Щось пішло не так. Спробуйте ще раз.",
1822
1824
  "notConfiguredTitle": "Автентифікацію не налаштовано",
1823
- "notConfiguredBody": "У цьому розгортанні не ввімкнено жодного способу входу, тому ви не можете увійти чи отримати доступ до своїх робочих просторів. Адміністратор має налаштувати постачальника автентифікації (OAuth GitHub або Google чи вхід за електронною поштою та паролем).",
1825
+ "notConfiguredBody": "У цьому розгортанні не ввімкнено жодного способу входу, тому ви не можете увійти чи отримати доступ до своїх робочих просторів. Адміністратор має налаштувати постачальника автентифікації (єдиний вхід через постачальника ідентифікації вашої організації, OAuth GitHub або Google чи вхід за електронною поштою та паролем).",
1824
1826
  "patPlaceholder": "Особистий токен доступу {provider}",
1825
1827
  "signInWithPat": "Увійти за допомогою PAT {provider}"
1826
1828
  },
1829
+ "sso": {
1830
+ "continueWith": "Продовжити з {provider}",
1831
+ "failedTitle": "Єдиний вхід не завершено",
1832
+ "errors": {
1833
+ "stateInvalid": "Ця спроба входу застаріла або вже була використана. Почніть знову з цієї сторінки.",
1834
+ "providerDenied": "Ваш постачальник ідентифікації відмовив у вході. Якщо ви не скасовували його самостійно, запитайте у своєї ІТ-команди, чи призначено вам цей застосунок.",
1835
+ "exchangeFailed": "Це розгортання не змогло завершити обмін із вашим постачальником ідентифікації. Адміністратор має перевірити клієнтський секрет і URL перенаправлення єдиного входу.",
1836
+ "tokenInvalid": "Не вдалося перевірити відповідь вашого постачальника ідентифікації. Адміністратор має перевірити налаштування єдиного входу цього розгортання.",
1837
+ "subjectMissing": "Постачальник ідентифікації не повернув ідентифікатора користувача, тому обліковий запис не вдалося визначити. Адміністратор має перевірити, які claims він передає.",
1838
+ "groupRequired": "Вхід відбувся успішно, але ви не належите до жодної групи каталогу, якій дозволено користуватися цим розгортанням. Попросіть ІТ-команду додати вас.",
1839
+ "domainNotAllowed": "Вашому домену електронної пошти не дозволено входити до цього розгортання. Запитайте в ІТ-команди, який обліковий запис використати.",
1840
+ "emailRequired": "Це розгортання обмежує вхід за доменом електронної пошти, але ваш постачальник ідентифікації не передав підтвердженої адреси. Адміністратор має увімкнути claim електронної пошти.",
1841
+ "providerUnreachable": "Ваш постачальник ідентифікації не відповів під час завершення входу, тож він або мережа до нього можуть бути недоступні. Спробуйте ще раз за мить, а якщо це повторюється, повідомте адміністратора.",
1842
+ "unknown": "Єдиний вхід не вдався з причини, якої ця версія не розпізнає. Спробуйте ще раз, а якщо проблема повторюється, повідомте адміністратора."
1843
+ }
1844
+ },
1827
1845
  "resetPassword": {
1828
1846
  "title": "Скинути пароль",
1829
1847
  "subtitle": "Виберіть новий пароль для свого облікового запису.",
@@ -4693,6 +4711,7 @@
4693
4711
  "threshold": "поріг {threshold}",
4694
4712
  "thresholdHint": "Оцінка, якої мав досягти цей крок, узята з політики злиття завдання. Нижче за неї суддя повертає роботу до кроку, що її створив, разом зі своїми зауваженнями на доопрацювання, або ставить запуск на паузу для вас, коли бюджет спроб вичерпано.",
4695
4713
  "rubricOverridden": "рубрика робочого простору",
4714
+ "modelPinUnavailable": "Цю перевірку написано для моделі {model}, яку це розгортання не може запустити, тож роботу оцінила інша модель.",
4696
4715
  "reworkRounds": "доопрацювання {spent}/{budget}",
4697
4716
  "findingsHeading": "Що позначила рубрика",
4698
4717
  "roundsHeading": "Раунди перевірки",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.229.0",
3
+ "version": "0.231.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",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.40",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.246.0"
43
+ "@cat-factory/contracts": "0.248.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",