@cat-factory/app 0.224.1 → 0.226.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.
@@ -22,22 +22,18 @@ type Entry =
22
22
  // clock-fallback `occurredAt`).
23
23
  const entries = computed<Entry[]>(() =>
24
24
  [
25
- ...props.failures.map(
26
- (failure, i): Entry => ({
27
- kind: 'failure',
28
- key: `failure-${i}`,
29
- occurredAt: failure.occurredAt,
30
- failure,
31
- }),
32
- ),
33
- ...props.outputs.map(
34
- (output, i): Entry => ({
35
- kind: 'success',
36
- key: `success-${i}`,
37
- occurredAt: output.occurredAt,
38
- output,
39
- }),
40
- ),
25
+ ...props.failures.map((failure, i): Entry => ({
26
+ kind: 'failure',
27
+ key: `failure-${i}`,
28
+ occurredAt: failure.occurredAt,
29
+ failure,
30
+ })),
31
+ ...props.outputs.map((output, i): Entry => ({
32
+ kind: 'success',
33
+ key: `success-${i}`,
34
+ occurredAt: output.occurredAt,
35
+ output,
36
+ })),
41
37
  ].sort((a, b) => b.occurredAt - a.occurredAt),
42
38
  )
43
39
  </script>
@@ -1,6 +1,9 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import type { RiskPolicy } from '~/types/merge'
3
- import { resolveRiskPolicyPicker } from '~/components/riskPolicy/RiskPolicyPicker.logic'
3
+ import {
4
+ refusedRiskPolicySelections,
5
+ resolveRiskPolicyPicker,
6
+ } from '~/components/riskPolicy/RiskPolicyPicker.logic'
4
7
 
5
8
  // The resolver reads ids only, so the fixture carries just enough to be identifiable.
6
9
  const policy = (id: string, name: string) => ({ id, name }) as unknown as RiskPolicy
@@ -73,3 +76,60 @@ describe('resolveRiskPolicyPicker', () => {
73
76
  expect(state).toEqual({ policy: null, viaWorkspaceDefault: false })
74
77
  })
75
78
  })
79
+
80
+ describe('refusedRiskPolicySelections', () => {
81
+ const rolePolicy = (id: string, over: Partial<RiskPolicy>) =>
82
+ ({ id, name: id, classRules: {}, classRulesByRole: {}, dryRunRoles: [], ...over }) as RiskPolicy
83
+
84
+ const sandboxed = rolePolicy('mp_sandboxed', { dryRunRoles: ['member'] })
85
+ const open = rolePolicy('mp_open', {})
86
+ const member = { role: 'member' as const, managesPolicy: false }
87
+
88
+ it('marks the rows a member may not move a sandboxed task to', () => {
89
+ // The picker must not offer what the backend refuses, including the "workspace default" row,
90
+ // which is a real policy (here: the open one) and not the absence of a choice.
91
+ const refused = refusedRiskPolicySelections({
92
+ options: [sandboxed, open],
93
+ defaultPolicy: open,
94
+ modelValue: 'mp_sandboxed',
95
+ actor: member,
96
+ })
97
+ expect(refused.get('mp_open')).toBe('relaxes_role_sandbox')
98
+ expect(refused.get('')).toBe('relaxes_role_sandbox')
99
+ expect(refused.has('mp_sandboxed')).toBe(false) // the row already selected
100
+ })
101
+
102
+ it('judges a task that picked nothing against the workspace default, as the engine does', () => {
103
+ // The create-form shape: nothing selected yet, so the policy being moved AWAY from is the one
104
+ // the task would have been governed by.
105
+ const refused = refusedRiskPolicySelections({
106
+ options: [sandboxed, open],
107
+ defaultPolicy: sandboxed,
108
+ modelValue: '',
109
+ actor: member,
110
+ })
111
+ expect(refused.get('mp_open')).toBe('relaxes_role_sandbox')
112
+ expect(refused.has('mp_sandboxed')).toBe(false)
113
+ })
114
+
115
+ it('refuses nothing to an editor who manages the policy library', () => {
116
+ const refused = refusedRiskPolicySelections({
117
+ options: [sandboxed, open],
118
+ defaultPolicy: open,
119
+ modelValue: 'mp_sandboxed',
120
+ actor: { role: 'admin', managesPolicy: true },
121
+ })
122
+ expect(refused.size).toBe(0)
123
+ })
124
+
125
+ it('refuses nothing between policies that treat every initiator alike', () => {
126
+ // Every built-in ships with an empty role layer, so the common case offers the whole library.
127
+ const refused = refusedRiskPolicySelections({
128
+ options: [open, rolePolicy('mp_other', {})],
129
+ defaultPolicy: open,
130
+ modelValue: 'mp_open',
131
+ actor: member,
132
+ })
133
+ expect(refused.size).toBe(0)
134
+ })
135
+ })
@@ -6,6 +6,11 @@
6
6
  // riskPolicies store's `resolve()` falls back to the workspace default for both an empty id
7
7
  // and a dangling one; this mirrors that, so the picker can never tell the user "no risk policy
8
8
  // configured" about a task the default is quietly governing.
9
+ import {
10
+ refuseRiskPolicySelection,
11
+ type BlockEditActor,
12
+ type RiskPolicySelectionRefusal,
13
+ } from '@cat-factory/contracts'
9
14
  import type { RiskPolicy } from '~/types/merge'
10
15
 
11
16
  export interface RiskPolicyPickerState {
@@ -45,3 +50,38 @@ export function resolveRiskPolicyPicker(input: RiskPolicyPickerInput): RiskPolic
45
50
  if (named) return { policy: named, viaWorkspaceDefault: false }
46
51
  return { policy: input.defaultPolicy, viaWorkspaceDefault: !!input.defaultPolicy }
47
52
  }
53
+
54
+ /**
55
+ * Which of the offered policies this user may not move THIS task to, keyed by option id (`''`
56
+ * for the "workspace default" row), with the reason.
57
+ *
58
+ * A task's policy decides whether its runs are sandboxed for the initiator's role and how their
59
+ * auto-merge is narrowed, so the backend refuses a selection that would relax what the selector's
60
+ * own role is held to (ADR 0037). The picker applies the SAME contracts rule rather than offering
61
+ * a row and handing back a 403: an authoring surface that offers what the engine discards is
62
+ * telling someone they made a choice they did not make.
63
+ *
64
+ * The policy being moved AWAY from is resolved exactly as the engine resolves it: the named
65
+ * option, else the workspace default. That is what makes the same call correct on the create form
66
+ * (nothing picked yet, so the default is what would have governed the task) and in the inspector.
67
+ */
68
+ export function refusedRiskPolicySelections(input: {
69
+ options: readonly RiskPolicy[]
70
+ defaultPolicy: RiskPolicy | null
71
+ modelValue: string
72
+ actor: BlockEditActor
73
+ }): Map<string, RiskPolicySelectionRefusal> {
74
+ const refusals = new Map<string, RiskPolicySelectionRefusal>()
75
+ const from =
76
+ (input.modelValue ? input.options.find((p) => p.id === input.modelValue) : undefined) ??
77
+ input.defaultPolicy
78
+ if (!from) return refusals
79
+ const judge = (id: string, to: RiskPolicy | null) => {
80
+ if (!to) return
81
+ const refusal = refuseRiskPolicySelection({ from, to, actor: input.actor })
82
+ if (refusal) refusals.set(id, refusal)
83
+ }
84
+ judge('', input.defaultPolicy)
85
+ for (const policy of input.options) judge(policy.id, policy)
86
+ return refusals
87
+ }
@@ -14,7 +14,10 @@
14
14
  import { computed, ref } from 'vue'
15
15
  import type { RiskPolicy } from '~/types/merge'
16
16
  import RiskPolicyPreview from '~/components/riskPolicy/RiskPolicyPreview.vue'
17
- import { resolveRiskPolicyPicker } from '~/components/riskPolicy/RiskPolicyPicker.logic'
17
+ import {
18
+ refusedRiskPolicySelections,
19
+ resolveRiskPolicyPicker,
20
+ } from '~/components/riskPolicy/RiskPolicyPicker.logic'
18
21
 
19
22
  const props = withDefaults(
20
23
  defineProps<{
@@ -37,6 +40,7 @@ const props = withDefaults(
37
40
 
38
41
  const emit = defineEmits<{ 'update:modelValue': [string] }>()
39
42
  const { t } = useI18n()
43
+ const access = useWorkspaceAccess()
40
44
 
41
45
  const open = ref(false)
42
46
  // The row the pointer or the keyboard is currently on, driving the right-column preview.
@@ -56,6 +60,39 @@ const preview = computed(() =>
56
60
  }),
57
61
  )
58
62
 
63
+ /**
64
+ * The policies this user may not move THIS task to, and why (ADR 0037): a selection may not
65
+ * relax what the selector's own role is held to, and the backend refuses one that does. Offering
66
+ * the row anyway would be telling someone they made a choice the engine then discards.
67
+ *
68
+ * Empty on every workspace whose policies treat each initiator alike (which is every built-in),
69
+ * and always empty for someone holding `settings.manage`, who owns the library.
70
+ */
71
+ const refused = computed(() =>
72
+ refusedRiskPolicySelections({
73
+ options: props.options,
74
+ defaultPolicy: props.defaultPolicy,
75
+ modelValue: props.modelValue,
76
+ actor: { role: access.role.value, managesPolicy: access.canManageSettings.value },
77
+ }),
78
+ )
79
+
80
+ /** The reason the previewed row is refused, if it is: the pane explains what the row cannot. */
81
+ const previewRefusal = computed(() => refused.value.get(activeId.value ?? props.modelValue))
82
+
83
+ /**
84
+ * The same reason as a tooltip on the row itself, `undefined` when the row is selectable.
85
+ *
86
+ * A refused row is `aria-disabled`, NOT `disabled`: a disabled button is unfocusable, so keyboard
87
+ * users could never land on it, and landing on it is precisely what puts the reason in the detail
88
+ * pane (the `@focus` handler below drives the preview). Refusing the click is `choose`'s job, so
89
+ * the row stays reachable by both routes and explains itself on both.
90
+ */
91
+ function refusalText(id: string): string | undefined {
92
+ const refusal = refused.value.get(id)
93
+ return refusal ? t(`riskPolicy.picker.refused.${refusal}`) : undefined
94
+ }
95
+
59
96
  /**
60
97
  * Tabbing BETWEEN two rows fires `focusout` on the one being left, so only focus leaving the
61
98
  * panel altogether may drop the preview back to the selection.
@@ -68,6 +105,7 @@ function onPanelFocusOut(event: FocusEvent) {
68
105
  }
69
106
 
70
107
  function choose(id: string) {
108
+ if (refused.value.has(id)) return
71
109
  emit('update:modelValue', id)
72
110
  open.value = false
73
111
  }
@@ -101,14 +139,22 @@ function choose(id: string) {
101
139
  <li>
102
140
  <button
103
141
  type="button"
104
- class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-start text-sm hover:bg-slate-800/60"
105
- :class="modelValue ? 'text-slate-300' : 'text-slate-100'"
142
+ class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-start text-sm"
143
+ :class="[
144
+ refused.has('') ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-800/60',
145
+ modelValue ? 'text-slate-300' : 'text-slate-100',
146
+ ]"
147
+ :aria-disabled="refused.has('')"
148
+ :title="refusalText('')"
106
149
  data-testid="risk-policy-option-none"
107
150
  @mouseenter="activeId = ''"
108
151
  @focus="activeId = ''"
109
152
  @click="choose('')"
110
153
  >
111
- <UIcon name="i-lucide-rotate-ccw" class="h-4 w-4 shrink-0 text-slate-400" />
154
+ <UIcon
155
+ :name="refused.has('') ? 'i-lucide-lock' : 'i-lucide-rotate-ccw'"
156
+ class="h-4 w-4 shrink-0 text-slate-400"
157
+ />
112
158
  <span class="flex-1 truncate">{{ noneLabel }}</span>
113
159
  <UIcon
114
160
  v-if="!modelValue"
@@ -120,14 +166,22 @@ function choose(id: string) {
120
166
  <li v-for="p in options" :key="p.id">
121
167
  <button
122
168
  type="button"
123
- class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-start text-sm hover:bg-slate-800/60"
124
- :class="modelValue === p.id ? 'text-slate-100' : 'text-slate-300'"
169
+ class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-start text-sm"
170
+ :class="[
171
+ refused.has(p.id) ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-800/60',
172
+ modelValue === p.id ? 'text-slate-100' : 'text-slate-300',
173
+ ]"
174
+ :aria-disabled="refused.has(p.id)"
175
+ :title="refusalText(p.id)"
125
176
  :data-testid="`risk-policy-option-${p.id}`"
126
177
  @mouseenter="activeId = p.id"
127
178
  @focus="activeId = p.id"
128
179
  @click="choose(p.id)"
129
180
  >
130
- <UIcon name="i-lucide-git-merge" class="h-4 w-4 shrink-0 text-slate-400" />
181
+ <UIcon
182
+ :name="refused.has(p.id) ? 'i-lucide-lock' : 'i-lucide-git-merge'"
183
+ class="h-4 w-4 shrink-0 text-slate-400"
184
+ />
131
185
  <span class="flex-1 truncate">{{ p.name }}</span>
132
186
  <UIcon
133
187
  v-if="modelValue === p.id"
@@ -141,6 +195,13 @@ function choose(id: string) {
141
195
  <!-- right: what the active (or selected) policy actually does -->
142
196
  <div class="w-1/2 overflow-y-auto p-3">
143
197
  <template v-if="preview.policy">
198
+ <p
199
+ v-if="previewRefusal"
200
+ class="mb-2 text-[11px] leading-snug text-amber-400"
201
+ data-testid="risk-policy-refusal"
202
+ >
203
+ {{ t(`riskPolicy.picker.refused.${previewRefusal}`) }}
204
+ </p>
144
205
  <p
145
206
  v-if="preview.viaWorkspaceDefault"
146
207
  class="mb-2 text-[11px] leading-snug text-slate-500"
@@ -38,12 +38,20 @@ const auth = useAuthStore()
38
38
  const store = usePublicApiKeysStore()
39
39
 
40
40
  /**
41
- * The minter to attribute a key to. `null` when the key predates the audit column (or was
42
- * minted with no session), so the row simply omits the segment. When the minter is the
43
- * signed-in user we show a localized "you"; otherwise the raw `usr_*` id (the list has no
44
- * user-name lookup — the audit id is the honest, non-misleading thing to show).
41
+ * The minter to attribute a key to. When the minter is the signed-in user we show a localized
42
+ * "you"; for another person, the raw `usr_*` id (the list has no user-name lookup, so the audit id
43
+ * is the honest, non-misleading thing to show).
44
+ *
45
+ * A key PROVISIONED HEADLESSLY (`POST /api/v1/keys`) has no user and names the KEY that minted
46
+ * it instead. Reading `createdByKeyId` here is not a nicety: a headless mint stores a null user,
47
+ * so without this branch it would render exactly like a key that predates the audit column, and
48
+ * "nobody knows who made this" would be shown for the one case the platform knows precisely.
49
+ * `null` stays reserved for genuinely unattributed keys, where the row omits the segment.
45
50
  */
46
51
  function minterLabel(key: PublicApiKey): string | null {
52
+ if (key.createdByKeyId) {
53
+ return t('settings.apiTokens.list.createdByKey', { id: key.createdByKeyId })
54
+ }
47
55
  if (!key.createdByUserId) return null
48
56
  return key.createdByUserId === auth.user?.id
49
57
  ? t('settings.apiTokens.list.createdByYou')
@@ -12,6 +12,7 @@ function key(over: Partial<PublicApiKey> = {}): PublicApiKey {
12
12
  label: 'CI',
13
13
  scope: 'write',
14
14
  createdByUserId: null,
15
+ createdByKeyId: null,
15
16
  createdAt: 1,
16
17
  lastUsedAt: null,
17
18
  revokedAt: null,
@@ -699,6 +699,7 @@
699
699
  "neverUsed": "nie verwendet",
700
700
  "createdBy": "erstellt von {user}",
701
701
  "createdByYou": "Ihnen",
702
+ "createdByKey": "API-Schlüssel {id}",
702
703
  "revoke": "Token widerrufen"
703
704
  },
704
705
  "add": {
@@ -6155,7 +6156,11 @@
6155
6156
  },
6156
6157
  "picker": {
6157
6158
  "workspaceDefaultCaption": "Gilt, weil diese Aufgabe keine eigene Richtlinie wählt.",
6158
- "noneHint": "Keine Risikorichtlinie konfiguriert. Jeder Pull Request wartet auf eine menschliche Prüfung."
6159
+ "noneHint": "Keine Risikorichtlinie konfiguriert. Jeder Pull Request wartet auf eine menschliche Prüfung.",
6160
+ "refused": {
6161
+ "relaxes_role_sandbox": "Für deine Rolle nicht verfügbar: Läufe dieser Aufgabe laufen in der Sandbox, und diese Richtlinie würde sie mergen lassen. Eine Workspace-Administration kann das ändern.",
6162
+ "relaxes_role_class_rule": "Für deine Rolle nicht verfügbar: Diese Richtlinie merged Änderungen automatisch, die du bei dieser Aufgabe prüfen musst. Eine Workspace-Administration kann das ändern."
6163
+ }
6159
6164
  },
6160
6165
  "preview": {
6161
6166
  "defaultBadge": "Standard",
@@ -3190,6 +3190,7 @@
3190
3190
  "neverUsed": "never used",
3191
3191
  "createdBy": "created by {user}",
3192
3192
  "createdByYou": "you",
3193
+ "createdByKey": "API key {id}",
3193
3194
  "revoke": "Revoke token"
3194
3195
  },
3195
3196
  "add": {
@@ -4743,7 +4744,11 @@
4743
4744
  },
4744
4745
  "picker": {
4745
4746
  "workspaceDefaultCaption": "Applied because this task picks no policy of its own.",
4746
- "noneHint": "No risk policy configured. Every pull request waits for a human review."
4747
+ "noneHint": "No risk policy configured. Every pull request waits for a human review.",
4748
+ "refused": {
4749
+ "relaxes_role_sandbox": "Not available for your role: this task’s runs are sandboxed, and this policy would let them merge. A workspace admin can change it.",
4750
+ "relaxes_role_class_rule": "Not available for your role: this policy auto-merges changes you are held to review on this task. A workspace admin can change it."
4751
+ }
4747
4752
  },
4748
4753
  "preview": {
4749
4754
  "defaultBadge": "Default",
@@ -2942,6 +2942,7 @@
2942
2942
  "neverUsed": "nunca usado",
2943
2943
  "createdBy": "creado por {user}",
2944
2944
  "createdByYou": "ti",
2945
+ "createdByKey": "la clave de API {id}",
2945
2946
  "revoke": "Revocar token"
2946
2947
  },
2947
2948
  "add": {
@@ -6022,7 +6023,11 @@
6022
6023
  },
6023
6024
  "picker": {
6024
6025
  "workspaceDefaultCaption": "Se aplica porque esta tarea no elige una política propia.",
6025
- "noneHint": "No hay ninguna política de riesgo configurada. Cada pull request espera una revisión humana."
6026
+ "noneHint": "No hay ninguna política de riesgo configurada. Cada pull request espera una revisión humana.",
6027
+ "refused": {
6028
+ "relaxes_role_sandbox": "No disponible para tu rol: las ejecuciones de esta tarea están en un entorno aislado y esta política permitiría fusionarlas. Un administrador del espacio de trabajo puede cambiarlo.",
6029
+ "relaxes_role_class_rule": "No disponible para tu rol: esta política fusiona automáticamente cambios que tú debes revisar en esta tarea. Un administrador del espacio de trabajo puede cambiarlo."
6030
+ }
6026
6031
  },
6027
6032
  "preview": {
6028
6033
  "defaultBadge": "Predeterminada",
@@ -2942,6 +2942,7 @@
2942
2942
  "neverUsed": "jamais utilisé",
2943
2943
  "createdBy": "créé par {user}",
2944
2944
  "createdByYou": "vous",
2945
+ "createdByKey": "la clé d’API {id}",
2945
2946
  "revoke": "Révoquer le jeton"
2946
2947
  },
2947
2948
  "add": {
@@ -6022,7 +6023,11 @@
6022
6023
  },
6023
6024
  "picker": {
6024
6025
  "workspaceDefaultCaption": "Appliquée parce que cette tâche ne choisit aucune politique.",
6025
- "noneHint": "Aucune politique de risque configurée. Chaque pull request attend une revue humaine."
6026
+ "noneHint": "Aucune politique de risque configurée. Chaque pull request attend une revue humaine.",
6027
+ "refused": {
6028
+ "relaxes_role_sandbox": "Indisponible pour votre rôle : les exécutions de cette tâche sont isolées, et cette politique les laisserait fusionner. Un administrateur de l’espace de travail peut le changer.",
6029
+ "relaxes_role_class_rule": "Indisponible pour votre rôle : cette politique fusionne automatiquement des changements que vous devez relire sur cette tâche. Un administrateur de l’espace de travail peut le changer."
6030
+ }
6026
6031
  },
6027
6032
  "preview": {
6028
6033
  "defaultBadge": "Par défaut",
@@ -3083,6 +3083,7 @@
3083
3083
  "neverUsed": "מעולם לא היה בשימוש",
3084
3084
  "createdBy": "נוצר על ידי {user}",
3085
3085
  "createdByYou": "אתה",
3086
+ "createdByKey": "מפתח API {id}",
3086
3087
  "revoke": "בטל אסימון"
3087
3088
  },
3088
3089
  "add": {
@@ -6022,7 +6023,11 @@
6022
6023
  },
6023
6024
  "picker": {
6024
6025
  "workspaceDefaultCaption": "חלה מפני שהמשימה הזו לא בוחרת מדיניות משלה.",
6025
- "noneHint": "לא הוגדרה מדיניות סיכון. כל בקשת משיכה ממתינה לסקירה אנושית."
6026
+ "noneHint": "לא הוגדרה מדיניות סיכון. כל בקשת משיכה ממתינה לסקירה אנושית.",
6027
+ "refused": {
6028
+ "relaxes_role_sandbox": "לא זמין לתפקיד שלך: ההרצות של משימה זו מבודדות, והמדיניות הזו הייתה מאפשרת למזג אותן. מנהל סביבת העבודה יכול לשנות זאת.",
6029
+ "relaxes_role_class_rule": "לא זמין לתפקיד שלך: המדיניות הזו ממזגת אוטומטית שינויים שאתה נדרש לבדוק במשימה זו. מנהל סביבת העבודה יכול לשנות זאת."
6030
+ }
6026
6031
  },
6027
6032
  "preview": {
6028
6033
  "defaultBadge": "ברירת מחדל",
@@ -699,6 +699,7 @@
699
699
  "neverUsed": "mai utilizzato",
700
700
  "createdBy": "creato da {user}",
701
701
  "createdByYou": "te",
702
+ "createdByKey": "la chiave API {id}",
702
703
  "revoke": "Revoca token"
703
704
  },
704
705
  "add": {
@@ -6155,7 +6156,11 @@
6155
6156
  },
6156
6157
  "picker": {
6157
6158
  "workspaceDefaultCaption": "Si applica perche questa attivita non sceglie un criterio proprio.",
6158
- "noneHint": "Nessun criterio di rischio configurato. Ogni pull request attende una revisione umana."
6159
+ "noneHint": "Nessun criterio di rischio configurato. Ogni pull request attende una revisione umana.",
6160
+ "refused": {
6161
+ "relaxes_role_sandbox": "Non disponibile per il tuo ruolo: le esecuzioni di questa attività sono isolate e questo criterio ne consentirebbe il merge. Un amministratore dello spazio di lavoro può cambiarlo.",
6162
+ "relaxes_role_class_rule": "Non disponibile per il tuo ruolo: questo criterio unisce automaticamente modifiche che devi revisionare in questa attività. Un amministratore dello spazio di lavoro può cambiarlo."
6163
+ }
6159
6164
  },
6160
6165
  "preview": {
6161
6166
  "defaultBadge": "Predefinito",
@@ -3083,6 +3083,7 @@
3083
3083
  "neverUsed": "未使用",
3084
3084
  "createdBy": "作成者: {user}",
3085
3085
  "createdByYou": "あなた",
3086
+ "createdByKey": "APIキー {id}",
3086
3087
  "revoke": "トークンを取り消す"
3087
3088
  },
3088
3089
  "add": {
@@ -6022,7 +6023,11 @@
6022
6023
  },
6023
6024
  "picker": {
6024
6025
  "workspaceDefaultCaption": "このタスクが独自のポリシーを選んでいないため、これが適用されます。",
6025
- "noneHint": "リスクポリシーが設定されていません。すべてのプルリクエストは人のレビューを待ちます。"
6026
+ "noneHint": "リスクポリシーが設定されていません。すべてのプルリクエストは人のレビューを待ちます。",
6027
+ "refused": {
6028
+ "relaxes_role_sandbox": "あなたのロールでは選べません。このタスクの実行はサンドボックスで動作しますが、このポリシーではマージが許可されます。ワークスペース管理者が変更できます。",
6029
+ "relaxes_role_class_rule": "あなたのロールでは選べません。このポリシーは、このタスクであなたのレビューが必要な変更を自動マージします。ワークスペース管理者が変更できます。"
6030
+ }
6026
6031
  },
6027
6032
  "preview": {
6028
6033
  "defaultBadge": "既定",
@@ -2942,6 +2942,7 @@
2942
2942
  "neverUsed": "nigdy nie użyto",
2943
2943
  "createdBy": "utworzone przez {user}",
2944
2944
  "createdByYou": "Ciebie",
2945
+ "createdByKey": "klucz API {id}",
2945
2946
  "revoke": "Unieważnij token"
2946
2947
  },
2947
2948
  "add": {
@@ -6022,7 +6023,11 @@
6022
6023
  },
6023
6024
  "picker": {
6024
6025
  "workspaceDefaultCaption": "Stosowana, ponieważ to zadanie nie wybiera własnej zasady.",
6025
- "noneHint": "Nie skonfigurowano żadnej zasady ryzyka. Każdy pull request czeka na ludzką recenzję."
6026
+ "noneHint": "Nie skonfigurowano żadnej zasady ryzyka. Każdy pull request czeka na ludzką recenzję.",
6027
+ "refused": {
6028
+ "relaxes_role_sandbox": "Niedostępne dla twojej roli: uruchomienia tego zadania działają w piaskownicy, a ta zasada pozwoliłaby je scalić. Administrator obszaru roboczego może to zmienić.",
6029
+ "relaxes_role_class_rule": "Niedostępne dla twojej roli: ta zasada automatycznie scala zmiany, które musisz przejrzeć w tym zadaniu. Administrator obszaru roboczego może to zmienić."
6030
+ }
6026
6031
  },
6027
6032
  "preview": {
6028
6033
  "defaultBadge": "Domyślna",
@@ -3083,6 +3083,7 @@
3083
3083
  "neverUsed": "hiç kullanılmadı",
3084
3084
  "createdBy": "oluşturan: {user}",
3085
3085
  "createdByYou": "siz",
3086
+ "createdByKey": "{id} API anahtarı",
3086
3087
  "revoke": "Belirteci iptal et"
3087
3088
  },
3088
3089
  "add": {
@@ -6022,7 +6023,11 @@
6022
6023
  },
6023
6024
  "picker": {
6024
6025
  "workspaceDefaultCaption": "Bu görev kendine bir ilke seçmediği için uygulanır.",
6025
- "noneHint": "Yapılandırılmış risk ilkesi yok. Her pull request insan incelemesini bekler."
6026
+ "noneHint": "Yapılandırılmış risk ilkesi yok. Her pull request insan incelemesini bekler.",
6027
+ "refused": {
6028
+ "relaxes_role_sandbox": "Rolün için kullanılamaz: bu görevin çalışmaları yalıtılmış çalışır ve bu ilke birleştirmelerine izin verirdi. Bir çalışma alanı yöneticisi bunu değiştirebilir.",
6029
+ "relaxes_role_class_rule": "Rolün için kullanılamaz: bu ilke, bu görevde incelemen gereken değişiklikleri otomatik birleştirir. Bir çalışma alanı yöneticisi bunu değiştirebilir."
6030
+ }
6026
6031
  },
6027
6032
  "preview": {
6028
6033
  "defaultBadge": "Varsayılan",
@@ -2942,6 +2942,7 @@
2942
2942
  "neverUsed": "ніколи не використовувався",
2943
2943
  "createdBy": "створено {user}",
2944
2944
  "createdByYou": "вами",
2945
+ "createdByKey": "ключ API {id}",
2945
2946
  "revoke": "Відкликати токен"
2946
2947
  },
2947
2948
  "add": {
@@ -6022,7 +6023,11 @@
6022
6023
  },
6023
6024
  "picker": {
6024
6025
  "workspaceDefaultCaption": "Застосовується, бо це завдання не обрало власної політики.",
6025
- "noneHint": "Політику ризику не налаштовано. Кожен pull request чекає на людську перевірку."
6026
+ "noneHint": "Політику ризику не налаштовано. Кожен pull request чекає на людську перевірку.",
6027
+ "refused": {
6028
+ "relaxes_role_sandbox": "Недоступно для вашої ролі: запуски цього завдання ізольовані, а ця політика дозволила б їх злиття. Адміністратор робочого простору може це змінити.",
6029
+ "relaxes_role_class_rule": "Недоступно для вашої ролі: ця політика автоматично зливає зміни, які ви маєте перевіряти в цьому завданні. Адміністратор робочого простору може це змінити."
6030
+ }
6026
6031
  },
6027
6032
  "preview": {
6028
6033
  "defaultBadge": "Типова",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.224.1",
3
+ "version": "0.226.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",
@@ -18,7 +18,7 @@
18
18
  "access": "public"
19
19
  },
20
20
  "dependencies": {
21
- "@modular-frontend/core": "^0.6.0",
21
+ "@modular-frontend/core": "0.6.0",
22
22
  "@modular-vue/core": "^1.5.0",
23
23
  "@modular-vue/journeys": "^1.4.0",
24
24
  "@modular-vue/nuxt": "^0.4.1",
@@ -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.241.0"
43
+ "@cat-factory/contracts": "0.243.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",
@@ -53,7 +53,7 @@
53
53
  "vue-tsc": "^3.3.9"
54
54
  },
55
55
  "peerDependencies": {
56
- "nuxt": "^4.5.0"
56
+ "nuxt": "^4.5.1"
57
57
  },
58
58
  "scripts": {
59
59
  "postinstall": "nuxt prepare",