@a5c-ai/krate 5.0.1-staging.69cb593ea → 5.0.1-staging.6be34ee2a

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.
@@ -0,0 +1,3732 @@
1
+ # Krate CRD Behaviors and Relationships
2
+
3
+ Exhaustive behavioral specification for all 76 Krate resource kinds, external backend
4
+ synchronization patterns, Gitea and GitHub integration, issue/project relationships,
5
+ and run/runner lifecycle with Argo CD and external pipeline integration.
6
+
7
+ **API Group:** `krate.a5c.ai`
8
+ **API Version:** `v1alpha1`
9
+ **Platform Namespace:** `krate-system` (configurable via `KRATE_NAMESPACE`)
10
+
11
+ ---
12
+
13
+ ## PART 1: Complete CRD Behavioral Specification
14
+
15
+ This section documents every resource kind in the Krate platform. Resources are divided
16
+ into two storage classes:
17
+
18
+ - **CONFIG_KINDS** (44 kinds): Stored in etcd via Kubernetes CRDs, managed declaratively
19
+ - **AGGREGATED_KINDS** (30 kinds): Stored in PostgreSQL, accessed via aggregated API
20
+
21
+ Additionally, the platform integrates with external CRDs:
22
+ - **KubeVela** (12 kinds): `core.oam.dev` group — delivery plane
23
+ - **Kyverno** (10 kinds): `kyverno.io` and `policies.kyverno.io` — policy engine
24
+
25
+ ---
26
+
27
+ ### 1.1 Identity Domain (8 kinds)
28
+
29
+ Resources that establish tenant boundaries, user identities, team membership,
30
+ invitations, identity federation, authentication providers, and agent service accounts.
31
+
32
+ ---
33
+
34
+ #### Organization
35
+
36
+ | Field | Value |
37
+ |-------|-------|
38
+ | Storage | etcd |
39
+ | Context | identity |
40
+ | Plural | organizations |
41
+ | Namespace | `krate-system` (platform-scoped) |
42
+
43
+ **Purpose:** Tenant boundary. Each organization owns exactly one namespace and all
44
+ resources within it. Organizations are the top-level isolation primitive.
45
+
46
+ **Required Spec Fields:**
47
+ - `displayName` — human-readable organization name
48
+ - `namespaceName` — the bound Kubernetes namespace (e.g., `krate-org-acme`)
49
+
50
+ **Derived Fields:**
51
+ - `spec.slug` — URL-safe identifier derived from metadata.name via `normalizeOrgSlug()`
52
+
53
+ **Behavior on Create/Apply:**
54
+ 1. `withOrgScope()` normalizes the slug and assigns `metadata.namespace = krate-system`
55
+ 2. `ensureNamespace()` is called to guarantee `krate-org-{slug}` namespace exists
56
+ 3. Labels are applied: `krate.a5c.ai/org: {slug}`, `krate.a5c.ai/namespace: krate-org-{slug}`
57
+ 4. If the namespace does not exist, `kubectl create namespace {name}` is executed
58
+
59
+ **Behavior on Login (auto-creation):**
60
+ - `registerLoginProfile()` in `auth.js` reads `KRATE_ADMIN_ORG` or `KRATE_ORG` (default: `'default'`)
61
+ - Creates or updates the Organization if it does not exist
62
+
63
+ **Relationships:**
64
+ - Owns: ALL other org-scoped resources in `krate-org-{slug}` namespace
65
+ - Referenced by: OrgNamespaceBinding (1:1 mapping)
66
+ - Platform scope: Organization and OrgNamespaceBinding live in `krate-system`
67
+
68
+ **Controller:** `kubernetes-controller.js` — `organizationNamespaces()` resolves all org namespaces for snapshot enumeration
69
+
70
+ **Web Pages:** `/orgs` (list), auto-created on first OAuth login
71
+
72
+ **Reconciliation Plan:**
73
+ - `createOrganization()` creates three resources atomically:
74
+ 1. Kubernetes Namespace manifest with org labels
75
+ 2. Organization resource in `krate-system`
76
+ 3. OrgNamespaceBinding resource in `krate-system`
77
+
78
+ ---
79
+
80
+ #### OrgNamespaceBinding
81
+
82
+ | Field | Value |
83
+ |-------|-------|
84
+ | Storage | etcd |
85
+ | Context | identity |
86
+ | Plural | orgnamespacebindings |
87
+ | Namespace | `krate-system` (platform-scoped) |
88
+
89
+ **Purpose:** Explicit binding from one Organization to exactly one tenant namespace.
90
+ Ensures that namespace ownership is auditable and that organizations cannot accidentally
91
+ share namespaces.
92
+
93
+ **Required Spec Fields:**
94
+ - `organizationRef` — reference to the Organization slug
95
+ - `namespace` — the target namespace name
96
+
97
+ **Behavior on Create/Apply:**
98
+ 1. `withOrgScope()` resolves org slug → namespace name
99
+ 2. `spec.createNamespace` defaults to `true`
100
+ 3. Labels propagated: `krate.a5c.ai/org`, `krate.a5c.ai/namespace`
101
+ 4. Additional labels from `spec.labels` are merged into namespace metadata
102
+
103
+ **Relationships:**
104
+ - 1:1 with Organization
105
+ - Determines which namespaces are scanned during `getControllerSnapshot()`
106
+ - Used by `organizationNamespaces()` to build the list of org-scoped namespaces
107
+
108
+ **Reconciliation:**
109
+ - Created atomically alongside Organization via `createOrganization()`
110
+ - Snapshot controller enumerates bindings to discover all tenant namespaces
111
+
112
+ ---
113
+
114
+ #### User
115
+
116
+ | Field | Value |
117
+ |-------|-------|
118
+ | Storage | etcd |
119
+ | Context | identity |
120
+ | Plural | users |
121
+ | Namespace | org-scoped (`krate-org-{slug}`) |
122
+
123
+ **Purpose:** Human account profile representing an organization member. Tracks sign-in
124
+ state, admin privileges, team membership, and linked external identities.
125
+
126
+ **Required Spec Fields:**
127
+ - `organizationRef` — owning organization
128
+ - `displayName` — full name
129
+ - `email` — primary email address
130
+
131
+ **Optional Spec Fields:**
132
+ - `username` — login handle
133
+ - `teams[]` — team membership list
134
+ - `groups[]` — group membership (e.g., `krate:platform-engineers`)
135
+ - `disabled` — boolean to suspend the user
136
+ - `admin` — boolean for platform admin privileges
137
+
138
+ **Behavior on Create (via OAuth):**
139
+ 1. `exchangeOAuthCodeForProfile()` exchanges authorization code for access token
140
+ 2. `normalizeProviderProfile()` maps provider-specific fields to canonical form
141
+ 3. `registerLoginProfile()` calls `mapLoginProfileToKrateIdentity()` which:
142
+ - Creates or updates a User resource
143
+ - Creates or updates an IdentityMapping resource
144
+ 4. Bootstrap admin detection: if `KRATE_ADMIN_USERNAME` matches, `admin: true`
145
+
146
+ **Reconciliation (identity access):**
147
+ - `identityAccessReconciliationPlan()` for User kind:
148
+ - Phase: `Active` (normal) or `Disabled` (suspended)
149
+ - Computes `repositoryIdentity` from username or metadata.name
150
+ - Computes `groups` array: `['krate:users', role-group, ...team-groups]`
151
+ - Sync intents:
152
+ - `workspace-identity`: ensure-user or suspend-user
153
+ - `repository-access`: ensure-repository-user or suspend-repository-user
154
+
155
+ **RBAC:**
156
+ - Label `role: admin|member` determines permission level
157
+ - Admin users get group `krate:platform-engineers`
158
+ - Non-admin users get group `krate:developers`
159
+
160
+ **Relationships:**
161
+ - Belongs to: Organization (via organizationRef)
162
+ - Has: IdentityMapping (1:N, one per provider)
163
+ - References: Team (via teams[] array)
164
+ - Referenced by: RepositoryPermission, AgentApproval, AgentDispatchRun
165
+
166
+ ---
167
+
168
+ #### Team
169
+
170
+ | Field | Value |
171
+ |-------|-------|
172
+ | Storage | etcd |
173
+ | Context | identity |
174
+ | Plural | teams |
175
+ | Namespace | org-scoped |
176
+
177
+ **Purpose:** Team membership group with maintainers and repository permission grants.
178
+ Teams provide a grouping mechanism for repository access control.
179
+
180
+ **Required Spec Fields:**
181
+ - `organizationRef` — owning organization
182
+ - `displayName` — team name
183
+
184
+ **Optional Spec Fields:**
185
+ - `members[]` — list of user references
186
+ - `maintainers[]` — subset of members with team management rights
187
+ - `repositoryGrants[]` — array of `{ repository, permission }` tuples
188
+
189
+ **Reconciliation:**
190
+ - Phase: always `Active`
191
+ - Reports `memberCount` and `maintainerCount`
192
+ - Sync intents:
193
+ - `workspace-identity`: sync-team-membership (members + maintainers)
194
+ - `repository-access`: one sync-team-repository-grant per repositoryGrant entry
195
+ - Conditions: `TeamMembershipProjected`, `RepositoryGrantsProjected`
196
+
197
+ **Relationships:**
198
+ - Belongs to: Organization
199
+ - Contains: Users (via members/maintainers arrays)
200
+ - Grants: RepositoryPermission (via repositoryGrants)
201
+ - Synced to: Gitea team (via `createTeam()`, `addTeamMember()`, `addTeamRepository()`)
202
+
203
+ ---
204
+
205
+ #### Invite
206
+
207
+ | Field | Value |
208
+ |-------|-------|
209
+ | Storage | etcd |
210
+ | Context | identity |
211
+ | Plural | invites |
212
+ | Namespace | org-scoped |
213
+
214
+ **Purpose:** Pending user invitation with requested teams and expiry. Tracks the
215
+ invitation lifecycle from creation through acceptance or expiration.
216
+
217
+ **Required Spec Fields:**
218
+ - `organizationRef` — owning organization
219
+ - `email` — invitee email address
220
+ - `role` — requested role (admin/member)
221
+
222
+ **Optional Spec Fields:**
223
+ - `expiresAt` — ISO 8601 expiration timestamp
224
+ - `teams[]` — teams to auto-assign on acceptance
225
+
226
+ **Lifecycle Phases:**
227
+ - `Pending` — invitation sent, awaiting acceptance
228
+ - `Accepted` — user has accepted; User resource created
229
+ - `Expired` — past expiresAt without acceptance
230
+ - `Revoked` — manually cancelled by admin
231
+
232
+ **Reconciliation:**
233
+ - Tracks `expiresAt` in status
234
+ - Sync intents:
235
+ - `workspace-identity`: send-invite (Pending) or close-invite (other phases)
236
+ - Condition: `InviteLifecycleTracked`
237
+
238
+ **Relationships:**
239
+ - Belongs to: Organization
240
+ - Creates: User (on acceptance)
241
+ - References: Teams (for auto-assignment)
242
+
243
+ ---
244
+
245
+ #### IdentityMapping
246
+
247
+ | Field | Value |
248
+ |-------|-------|
249
+ | Storage | etcd |
250
+ | Context | identity |
251
+ | Plural | identitymappings |
252
+ | Namespace | org-scoped |
253
+
254
+ **Purpose:** Mapping between Krate user accounts, sign-in provider subjects, workspace
255
+ identities, and repository hosting accounts. Enables federation across multiple
256
+ identity providers.
257
+
258
+ **Required Spec Fields:**
259
+ - `organizationRef` — owning organization
260
+ - `user` — reference to local User resource name
261
+ - `provider` — identity provider ID (e.g., 'github', 'sso', 'delegated')
262
+ - `subject` — external subject identifier (e.g., GitHub user ID)
263
+
264
+ **Optional Spec Fields:**
265
+ - `workspaceIdentity` — `{ name }` workspace identity binding
266
+ - `repositoryIdentity` — `{ username }` git hosting identity binding
267
+
268
+ **Behavior on Create:**
269
+ - Created automatically during OAuth login callback
270
+ - `mapLoginProfileToKrateIdentity()` in `identity-policy.js` generates both User and IdentityMapping
271
+
272
+ **Reconciliation:**
273
+ - Phase: `Synced` (all fields present) or `Pending` (missing user/provider/subject)
274
+ - Reports `workspaceIdentity` and `repositoryIdentity` in status
275
+ - Sync intents:
276
+ - `workspace-identity`: link-identity (user, provider, subject)
277
+ - `repository-access`: link-repository-identity (user, repositoryIdentity)
278
+ - Conditions: `WorkspaceIdentityProjected`, `RepositoryIdentityProjected`
279
+
280
+ **Relationships:**
281
+ - Belongs to: User (via user field)
282
+ - Links: external provider subject → local user
283
+ - Used by: auth system to resolve login → User mapping
284
+
285
+ ---
286
+
287
+ #### AuthProvider
288
+
289
+ | Field | Value |
290
+ |-------|-------|
291
+ | Storage | etcd |
292
+ | Context | identity |
293
+ | Plural | authproviders |
294
+ | Namespace | org-scoped |
295
+
296
+ **Purpose:** Installation sign-in provider configuration including visibility, OAuth
297
+ endpoints, and delegated identity settings. Controls which authentication methods
298
+ are available.
299
+
300
+ **Required Spec Fields:**
301
+ - `organizationRef` — owning organization
302
+ - `type` — provider type ('github', 'oidc', 'delegated')
303
+
304
+ **Optional Spec Fields (via environment):**
305
+ - `clientId` — OAuth client ID
306
+ - `clientSecret` — OAuth client secret (never stored in spec, env-only)
307
+ - `authorizationUrl` — OAuth authorization endpoint
308
+ - `tokenUrl` — OAuth token exchange endpoint
309
+ - `userInfoUrl` — OIDC userinfo endpoint
310
+ - `scopes` — space-separated OAuth scopes
311
+ - `enabled` — boolean toggle
312
+
313
+ **Provider Types:**
314
+ 1. **GitHub OAuth**: `KRATE_AUTH_GITHUB_*` environment variables
315
+ 2. **SSO (OIDC)**: `KRATE_AUTH_SSO_*` environment variables
316
+ 3. **Delegated Identity**: proxy-header-based auth (`x-forwarded-user`, etc.)
317
+
318
+ **Configuration (via `createAuthProviderConfig()`):**
319
+ - Session cookie name: `KRATE_AUTH_COOKIE_NAME` (default: `krate_session`)
320
+ - Delegated identity local development mode for testing without real OAuth
321
+ - Session secret for HMAC-signed cookies: `KRATE_SESSION_SECRET`
322
+
323
+ **Relationships:**
324
+ - Used by: auth middleware, login flow
325
+ - Produces: IdentityMapping (on successful login)
326
+ - Referenced by: IdentityMapping.spec.provider
327
+
328
+ ---
329
+
330
+ #### AgentServiceAccount
331
+
332
+ | Field | Value |
333
+ |-------|-------|
334
+ | Storage | etcd |
335
+ | Context | identity |
336
+ | Plural | agentserviceaccounts |
337
+ | Namespace | org-scoped |
338
+
339
+ **Purpose:** Kubernetes ServiceAccount wrapper for agent and runner identity binding.
340
+ Provides the runtime identity that agent pods use to authenticate to the Kubernetes
341
+ API and external services.
342
+
343
+ **Required Spec Fields:**
344
+ - `organizationRef` — owning organization
345
+ - `namespace` — target K8s namespace for the ServiceAccount
346
+ - `serviceAccountName` — name of the K8s ServiceAccount to bind
347
+
348
+ **Behavior:**
349
+ - Referenced by AgentStack via `spec.runtimeIdentity.serviceAccountRef`
350
+ - Stack reconciliation checks `RuntimeIdentityReady` condition against this resource
351
+ - Used to generate pod specs with `serviceAccountName` field
352
+
353
+ **Relationships:**
354
+ - Referenced by: AgentStack (runtimeIdentity)
355
+ - Controls: RBAC access for agent pods via AgentRoleBinding
356
+ - Bound to: Kubernetes ServiceAccount in the cluster
357
+
358
+ ---
359
+
360
+ ### 1.2 Repository Domain (7 kinds)
361
+
362
+ Resources managing git repository abstractions, deploy keys, collaborator permissions,
363
+ branch protection rules, reference policies, webhook subscriptions, and runner pools.
364
+
365
+ ---
366
+
367
+ #### Repository
368
+
369
+ | Field | Value |
370
+ |-------|-------|
371
+ | Storage | etcd |
372
+ | Context | data-plane |
373
+ | Plural | repositories |
374
+ | Namespace | org-scoped |
375
+
376
+ **Purpose:** Git repository abstraction over the Gitea backend (or external git hosts).
377
+ Manages repository identity, visibility, default branch, and integration with the
378
+ repository hosting layer.
379
+
380
+ **Required Spec Fields:**
381
+ - `organizationRef` — owning organization
382
+ - `visibility` — one of `public`, `internal`, `private`
383
+
384
+ **Optional Spec Fields:**
385
+ - `defaultBranch` — default branch name (default: `main`)
386
+
387
+ **Behavior on Create/Apply:**
388
+ 1. `repositoryManifest()` constructs the resource with org scope
389
+ 2. `applyResource()` calls `withOrgScope()` to resolve namespace
390
+ 3. `ensureNamespace()` ensures the org namespace exists
391
+ 4. `kubectl apply -f -` persists the CRD
392
+ 5. Reconciler emits sync intent: `ensure-gitea-repository`
393
+
394
+ **Gitea Integration:**
395
+ - `createGiteaBackend().createRepository({ owner, name, private, defaultBranch })`
396
+ - Owner is the Gitea organization corresponding to the Krate org
397
+ - Private flag derived from visibility (private/internal → private, public → public)
398
+ - Repository name matches `metadata.name`
399
+
400
+ **External Backend Integration:**
401
+ - When an ExternalBackendBinding exists with matching repository scope:
402
+ - Repository metadata synced bidirectionally with GitHub/GitLab
403
+ - ExternalObjectLink created mapping local repo → external repo ID
404
+ - Webhook registered on external provider for push/PR events
405
+
406
+ **Reconciliation (via `reconcileRepository()`):**
407
+ - Phase: `Reconciling` → `Ready`
408
+ - `gitBackend: 'gitea'` in status
409
+ - Conditions: `ResourceObserved`, `DataPlaneSyncPlanned`
410
+ - Sync intents:
411
+ - `git-data-plane`: ensure-gitea-repository
412
+ - `policy-controller`: compile-ref-policy
413
+
414
+ **Relationships:**
415
+ - Belongs to: Organization
416
+ - Has: SSHKey, RepositoryPermission, BranchProtection, RefPolicy
417
+ - Referenced by: AgentDispatchRun, Pipeline, Issue, PullRequest, KrateWorkspace
418
+ - Synced to: Gitea repository, GitHub repository (via ExternalBackendBinding)
419
+
420
+ **Web Pages:** `/repositories` (list), `/repositories/{name}/code` (browser),
421
+ `/repositories/{name}/settings`
422
+
423
+ ---
424
+
425
+ #### SSHKey
426
+
427
+ | Field | Value |
428
+ |-------|-------|
429
+ | Storage | etcd |
430
+ | Context | data-plane |
431
+ | Plural | sshkeys |
432
+ | Namespace | org-scoped |
433
+
434
+ **Purpose:** User, deploy, and automation SSH keys reconciled into repository key APIs.
435
+ Manages the lifecycle of SSH keys used for git authentication.
436
+
437
+ **Required Spec Fields:**
438
+ - `organizationRef` — owning organization
439
+ - `scope` — key scope (e.g., 'deploy', 'user', 'automation')
440
+ - `key` — public key material (SSH format)
441
+
442
+ **Optional Spec Fields:**
443
+ - `readOnly` — boolean (default: false for user keys, true for deploy keys)
444
+ - `owner` / `user` — the user or automation that owns this key
445
+ - `revoked` — boolean to mark key as revoked
446
+
447
+ **Reconciliation:**
448
+ - Phase: `Synced` (active) or `Revoked` (disabled)
449
+ - Fingerprint computed: `sha256:{base64url hash of key material}`
450
+ - Sync intents:
451
+ - `repository-access`: sync-ssh-key or revoke-ssh-key
452
+ - Condition: `SSHKeyProjected`
453
+
454
+ **Gitea Integration:**
455
+ - `createGiteaBackend().addDeployKey({ owner, repo, title, key, readOnly })`
456
+ - `createGiteaBackend().addUserSshKey({ title, key, readOnly })`
457
+ - Deploy keys are scoped to specific repositories
458
+ - User keys provide access across all repositories the user can access
459
+
460
+ **External (GitHub) Integration:**
461
+ - `GitHubGitForge.syncDeployKeys({ repo, desiredKeys })` — adds missing, removes extra
462
+ - Bidirectional sync: keys created in Krate → pushed to GitHub; keys from GitHub → synced to Krate
463
+
464
+ **Relationships:**
465
+ - Belongs to: Organization, optionally scoped to a Repository
466
+ - Synced to: Gitea deploy keys, GitHub deploy keys
467
+
468
+ ---
469
+
470
+ #### RepositoryPermission
471
+
472
+ | Field | Value |
473
+ |-------|-------|
474
+ | Storage | etcd |
475
+ | Context | data-plane |
476
+ | Plural | repositorypermissions |
477
+ | Namespace | org-scoped |
478
+
479
+ **Purpose:** Repository collaborator and team access permissions synced with the
480
+ repository hosting backend.
481
+
482
+ **Required Spec Fields:**
483
+ - `organizationRef` — owning organization
484
+ - `repository` — target repository name
485
+ - `subject` — user or team name
486
+ - `permission` — access level: `read`, `write`, `admin`
487
+
488
+ **Optional Spec Fields:**
489
+ - `subjectKind` — `user` or `team` (default: `user`)
490
+ - `revoked` — boolean to revoke access
491
+
492
+ **Reconciliation:**
493
+ - Phase: `Synced` (active) or `Revoked` (disabled)
494
+ - Reports `repository`, `subject`, `permission` in status
495
+ - Sync intents:
496
+ - `repository-access`: sync-repository-permission or revoke-repository-permission
497
+ - Condition: `RepositoryPermissionProjected`
498
+
499
+ **Gitea Integration:**
500
+ - `createGiteaBackend().addCollaborator({ owner, repo, username, permission })`
501
+ - `createGiteaBackend().addTeamRepository({ org, team, repo, permission })`
502
+ - Permission levels map directly to Gitea collaborator permissions
503
+
504
+ **External (GitHub) Integration:**
505
+ - Collaborators synced via GitHub REST API when ExternalBackendBinding exists
506
+ - Permission mapping: read→pull, write→push, admin→admin
507
+
508
+ **Relationships:**
509
+ - Belongs to: Organization, Repository
510
+ - References: User or Team (via subject)
511
+ - Synced to: Gitea collaborators, GitHub collaborators
512
+
513
+ ---
514
+
515
+ #### BranchProtection
516
+
517
+ | Field | Value |
518
+ |-------|-------|
519
+ | Storage | etcd |
520
+ | Context | control-plane |
521
+ | Plural | branchprotections |
522
+ | Namespace | org-scoped |
523
+
524
+ **Purpose:** Protected reference rules such as required reviews, status checks,
525
+ force-push policy, and merge requirements.
526
+
527
+ **Required Spec Fields:**
528
+ - `organizationRef` — owning organization
529
+ - `refs` — branch pattern(s) to protect (e.g., `['main', 'release/*']`)
530
+
531
+ **Optional Spec Fields:**
532
+ - `requiredReviews` — number of required approving reviews (default: 1)
533
+ - `statusChecks[]` — required status check contexts
534
+ - `allowForcePush` — boolean (default: false)
535
+ - `dismissStaleReviews` — boolean
536
+ - `enforceAdmins` — boolean
537
+
538
+ **Gitea Integration:**
539
+ - `createGiteaBackend().protectBranch({ owner, repo, branch, approvals, statusChecks })`
540
+ - Maps to Gitea branch protection rules with push whitelist, required approvals
541
+
542
+ **External (GitHub) Integration:**
543
+ - `GitHubGitForge.syncBranchProtection({ repo, branch, requiredReviews, requiredStatusChecks, dismissStaleReviews, enforceAdmins })`
544
+ - Full bidirectional sync with GitHub branch protection rules
545
+
546
+ **Relationships:**
547
+ - Belongs to: Organization, implicitly scoped to Repository
548
+ - Enforced by: Gitea, GitHub (via sync)
549
+ - Referenced by: CI pipeline gates
550
+
551
+ ---
552
+
553
+ #### RefPolicy
554
+
555
+ | Field | Value |
556
+ |-------|-------|
557
+ | Storage | etcd |
558
+ | Context | data-plane |
559
+ | Plural | refpolicies |
560
+ | Namespace | org-scoped |
561
+
562
+ **Purpose:** Reference deny rules, force-push policy, signing requirements, and custom
563
+ hook gates. Provides fine-grained control over what operations are allowed on refs.
564
+
565
+ **Required Spec Fields:**
566
+ - `organizationRef` — owning organization
567
+
568
+ **Optional Spec Fields:**
569
+ - `denyRules[]` — patterns that are denied (e.g., `['refs/heads/main']`)
570
+ - `forcePushPolicy` — `deny` | `allow` | `allowWithReview`
571
+ - `signingPolicy` — `required` | `optional` | `disabled`
572
+ - `hookGates[]` — custom pre-receive hook configurations
573
+
574
+ **Behavior:**
575
+ - Reconciler emits sync intent: `policy-controller: compile-ref-policy`
576
+ - Compiled into server-side hooks on the git backend
577
+ - Evaluated on push operations before accepting commits
578
+
579
+ **Relationships:**
580
+ - Belongs to: Organization, optionally scoped to Repository
581
+ - Complements: BranchProtection (ref policies are lower-level)
582
+ - Evaluated by: git pre-receive hooks
583
+
584
+ ---
585
+
586
+ #### WebhookSubscription
587
+
588
+ | Field | Value |
589
+ |-------|-------|
590
+ | Storage | etcd |
591
+ | Context | hooks-events |
592
+ | Plural | webhooksubscriptions |
593
+ | Namespace | org-scoped |
594
+
595
+ **Purpose:** Outbound webhook endpoint configuration with event filters, signing
596
+ reference, delivery mode, and retry policy.
597
+
598
+ **Required Spec Fields:**
599
+ - `organizationRef` — owning organization
600
+ - `url` — target webhook URL
601
+ - `events` — array of event types to subscribe to
602
+
603
+ **Optional Spec Fields:**
604
+ - `secret` — shared secret for HMAC signing
605
+ - `contentType` — `json` (default) or `form`
606
+ - `active` — boolean toggle
607
+ - `retryPolicy` — `{ maxRetries, backoffMs }`
608
+
609
+ **Gitea Integration:**
610
+ - `createGiteaBackend().createWebhook({ owner, repo, url, events, secret })`
611
+ - Registered on the Gitea repository for push/PR/issue events
612
+
613
+ **Relationships:**
614
+ - Belongs to: Organization, optionally scoped to Repository
615
+ - Produces: WebhookDelivery records (outbound attempts)
616
+ - Consumed by: WebhookController for outbound delivery
617
+
618
+ ---
619
+
620
+ #### RunnerPool
621
+
622
+ | Field | Value |
623
+ |-------|-------|
624
+ | Storage | etcd |
625
+ | Context | runners-ci |
626
+ | Plural | runnerpools |
627
+ | Namespace | org-scoped |
628
+
629
+ **Purpose:** Runner capacity pool with warm/max replicas, container image, cache policy,
630
+ trust boundary, and scaling configuration.
631
+
632
+ **Required Spec Fields:**
633
+ - `organizationRef` — owning organization
634
+ - `warmReplicas` — minimum number of idle runners to maintain (non-negative integer)
635
+ - `maxReplicas` — maximum runners allowed (positive integer, must be >= warmReplicas)
636
+
637
+ **Optional Spec Fields:**
638
+ - `image` — container image for runners (default: `ubuntu:24.04`)
639
+ - `trustTier` — `trusted` or `untrusted`
640
+ - `cache` — `{ type: 'object-storage' }` cache configuration
641
+ - `scalingMetric` — `queueDepth` (default)
642
+ - `serviceAccount` — K8s service account name for runners
643
+ - `resourceLimits` — `{ cpu, memory }` pod resource limits
644
+ - `resourceRequests` — `{ cpu, memory }` pod resource requests
645
+
646
+ **Validation (`validateRunnerPool()`):**
647
+ - metadata.name required
648
+ - organizationRef required
649
+ - warmReplicas: non-negative integer
650
+ - maxReplicas: positive integer >= warmReplicas
651
+
652
+ **Pool Status (`getPoolStatus()`):**
653
+ - `phase`: Empty | Active | Idle
654
+ - `scaling`: ScalingUp | ScalingDown | Stable
655
+ - Tracks: idle, active, terminating, total runner counts
656
+
657
+ **Capacity (`getCapacity()`):**
658
+ - `used`: runners currently Running
659
+ - `available`: maxReplicas - used
660
+ - `utilizationPct`: percentage of capacity in use
661
+
662
+ **Pod Spec Generation (`generatePodSpec()`):**
663
+ - Container: runner image with env vars (KRATE_ORG, KRATE_RUN_ID, KRATE_WORKSPACE_PATH)
664
+ - Volumes: workspace PVC mounted at /workspace
665
+ - Labels: krate.a5c.ai/runner, krate.a5c.ai/pool, krate.a5c.ai/org
666
+ - Service account: configured or default `krate-runner`
667
+ - Restart policy: Never
668
+
669
+ **Relationships:**
670
+ - Belongs to: Organization
671
+ - Contains: Runners (in-memory registry)
672
+ - Schedules: Jobs from Pipelines and AgentDispatchRuns
673
+ - Referenced by: AgentStack (runner policy)
674
+
675
+ ---
676
+
677
+ ### 1.3 Policy Domain (4 kinds)
678
+
679
+ Resources that manage policy posture, template libraries, binding/enforcement,
680
+ and exception workflows via Kyverno integration.
681
+
682
+ ---
683
+
684
+ #### PolicyProfile
685
+
686
+ | Field | Value |
687
+ |-------|-------|
688
+ | Storage | etcd |
689
+ | Context | policy |
690
+ | Plural | policyprofiles |
691
+ | Namespace | org-scoped |
692
+
693
+ **Purpose:** Organization-level policy posture configuration. Defines default templates,
694
+ rollout mode (audit vs enforce), and exception approval rules.
695
+
696
+ **Required Spec Fields:**
697
+ - `organizationRef` — owning organization
698
+ - `displayName` — profile name
699
+ - `mode` — rollout mode: `audit`, `enforce`, `disabled`
700
+
701
+ **Optional Spec Fields:**
702
+ - `defaultTemplates[]` — PolicyTemplate references to apply by default
703
+ - `exceptionApprovalPolicy` — rules for approving PolicyExceptionRequests
704
+ - `rolloutSchedule` — when to transition from audit to enforce
705
+
706
+ **Relationships:**
707
+ - Belongs to: Organization
708
+ - References: PolicyTemplate (defaults)
709
+ - Controls: PolicyBinding enforcement mode
710
+ - Evaluated by: Kyverno (when installed)
711
+
712
+ ---
713
+
714
+ #### PolicyTemplate
715
+
716
+ | Field | Value |
717
+ |-------|-------|
718
+ | Storage | etcd |
719
+ | Context | policy |
720
+ | Plural | policytemplates |
721
+ | Namespace | org-scoped |
722
+
723
+ **Purpose:** Curated Kyverno policy template with parameters, rollout defaults,
724
+ target kinds, and remediation guidance.
725
+
726
+ **Required Spec Fields:**
727
+ - `displayName` — template name
728
+ - `targetKinds` — resource kinds this policy applies to
729
+ - `kyverno` — Kyverno policy definition (ClusterPolicy or Policy body)
730
+
731
+ **Optional Spec Fields:**
732
+ - `parameters` — configurable parameters with defaults
733
+ - `rolloutDefault` — default mode when bound (audit/enforce)
734
+ - `remediation` — guidance text for violations
735
+ - `severity` — low/medium/high/critical
736
+
737
+ **Behavior:**
738
+ - Templates are the library of available policies
739
+ - Binding a template creates the actual Kyverno policy resources
740
+ - Parameters are substituted into the Kyverno spec at binding time
741
+
742
+ **Relationships:**
743
+ - Referenced by: PolicyBinding, PolicyProfile
744
+ - Produces: Kyverno ClusterPolicy/Policy (when bound and Kyverno installed)
745
+
746
+ ---
747
+
748
+ #### PolicyBinding
749
+
750
+ | Field | Value |
751
+ |-------|-------|
752
+ | Storage | etcd |
753
+ | Context | policy |
754
+ | Plural | policybindings |
755
+ | Namespace | org-scoped |
756
+
757
+ **Purpose:** Binding from a policy template to organization, repository, environment,
758
+ or resource selectors with audit/enforce rollout state.
759
+
760
+ **Required Spec Fields:**
761
+ - `organizationRef` — owning organization
762
+ - `templateRef` — reference to PolicyTemplate
763
+ - `mode` — current mode: `audit`, `enforce`, `disabled`
764
+
765
+ **Optional Spec Fields:**
766
+ - `scope` — `{ repositories[], environments[], labels }` targeting
767
+ - `parameters` — overrides for template parameters
768
+ - `exceptions[]` — PolicyExceptionRequest references
769
+
770
+ **Behavior:**
771
+ - When mode is `enforce` and Kyverno is installed:
772
+ - Generates a KyvernoPolicy or KyvernoClusterPolicy resource
773
+ - Policy violations block resource admission
774
+ - When mode is `audit`:
775
+ - Policy runs in audit mode; violations appear in PolicyReports
776
+ - When Kyverno is NOT installed:
777
+ - `requireForEnforceMode` env check; policies page shows info banner
778
+
779
+ **Relationships:**
780
+ - Belongs to: Organization
781
+ - References: PolicyTemplate
782
+ - Produces: Kyverno policy resources
783
+ - Has: PolicyExceptionRequest (exceptions to this binding)
784
+
785
+ ---
786
+
787
+ #### PolicyExceptionRequest
788
+
789
+ | Field | Value |
790
+ |-------|-------|
791
+ | Storage | etcd |
792
+ | Context | policy |
793
+ | Plural | policyexceptionrequests |
794
+ | Namespace | org-scoped |
795
+
796
+ **Purpose:** Auditable request and approval workflow for temporary Kyverno
797
+ PolicyException resources. Allows teams to request time-limited exceptions to
798
+ enforced policies.
799
+
800
+ **Required Spec Fields:**
801
+ - `organizationRef` — owning organization
802
+ - `policyRef` — reference to the PolicyBinding being excepted
803
+ - `justification` — reason for the exception
804
+ - `expiresAt` — ISO 8601 expiration timestamp
805
+
806
+ **Lifecycle:**
807
+ - `Pending` — request submitted, awaiting approval
808
+ - `Approved` — approved; KyvernoPolicyException created
809
+ - `Denied` — rejected by policy admin
810
+ - `Expired` — past expiresAt; exception removed
811
+ - `Revoked` — manually cancelled
812
+
813
+ **Behavior:**
814
+ - On approval: creates `KyvernoPolicyException` resource in policy namespace
815
+ - On expiry/revocation: deletes the KyvernoPolicyException
816
+ - Full audit trail maintained in resource history
817
+
818
+ **Relationships:**
819
+ - Belongs to: Organization
820
+ - References: PolicyBinding (the excepted policy)
821
+ - Produces: KyvernoPolicyException (when approved and Kyverno installed)
822
+
823
+ ---
824
+
825
+ ### 1.4 Web UI Domain (2 kinds)
826
+
827
+ ---
828
+
829
+ #### View
830
+
831
+ | Field | Value |
832
+ |-------|-------|
833
+ | Storage | etcd |
834
+ | Context | web-ui |
835
+ | Plural | views |
836
+ | Namespace | org-scoped |
837
+
838
+ **Purpose:** Saved triage and dashboard view backed by resource selectors. Allows users
839
+ to create custom filtered views of resources.
840
+
841
+ **Required Spec Fields:**
842
+ - `organizationRef` — owning organization
843
+ - `selector` — reference to a Selector resource or inline selector spec
844
+
845
+ **Optional Spec Fields:**
846
+ - `columns[]` — column definitions for table display
847
+ - `sort[]` — sort order specifications
848
+
849
+ **Relationships:**
850
+ - Belongs to: Organization
851
+ - References: Selector (filter definition)
852
+ - Displayed by: web console dashboard
853
+
854
+ ---
855
+
856
+ #### Selector
857
+
858
+ | Field | Value |
859
+ |-------|-------|
860
+ | Storage | etcd |
861
+ | Context | web-ui |
862
+ | Plural | selectors |
863
+ | Namespace | org-scoped |
864
+
865
+ **Purpose:** Reusable label/query selector for workflows and views. Encapsulates filter
866
+ criteria that can be shared across multiple Views.
867
+
868
+ **Required Spec Fields:**
869
+ - `organizationRef` — owning organization
870
+
871
+ **Optional Spec Fields:**
872
+ - `labels` — label selector map
873
+ - `query` — free-text query string
874
+
875
+ **Helper:** `createSelector()` factory function in resource-model.js
876
+
877
+ **Relationships:**
878
+ - Belongs to: Organization
879
+ - Referenced by: View resources
880
+
881
+ ---
882
+
883
+ ### 1.5 Agent Domain (28 kinds)
884
+
885
+ The agent domain is the largest, comprising resources for AI agent orchestration,
886
+ dispatch, sessions, tools, providers, memory, workspaces, projects, and approval gates.
887
+
888
+ ---
889
+
890
+ #### AgentStack
891
+
892
+ | Field | Value |
893
+ |-------|-------|
894
+ | Storage | etcd |
895
+ | Context | agents |
896
+ | Plural | agentstacks |
897
+ | Namespace | org-scoped |
898
+
899
+ **Purpose:** Reusable agent definition — the "recipe" for an agent. Specifies the base
900
+ agent, adapter, provider, model, prompt templates, MCP server references, skill
901
+ references, subagent references, context labels, approval mode, and runner policy.
902
+
903
+ **Required Spec Fields:**
904
+ - `organizationRef` — owning organization
905
+ - `baseAgent` — agent type identifier
906
+ - `adapter` — adapter reference (e.g., 'claude-code', 'openai')
907
+ - `runtimeIdentity` — AgentServiceAccount reference or inline config
908
+
909
+ **Optional Spec Fields:**
910
+ - `provider` — model provider (e.g., 'anthropic', 'openai')
911
+ - `model` — model identifier
912
+ - `promptTemplates` — system/user prompt fragments
913
+ - `toolPolicy` / `toolPolicyRef` — reference to AgentToolProfile
914
+ - `mcpServerRefs[]` — references to AgentMcpServer resources
915
+ - `skillRefs[]` — references to AgentSkill resources
916
+ - `subagentRefs[]` — references to AgentSubagent resources
917
+ - `contextLabelRefs[]` — references to AgentContextLabel resources
918
+ - `approvalMode` — when to require human approval
919
+ - `workspacePolicy` — reference to KrateWorkspacePolicy
920
+ - `taskKind` — default task kind
921
+
922
+ **Stack Reconciliation (`reconcileStack()`):**
923
+ Produces conditions indicating readiness:
924
+ 1. `CapabilitiesResolved` — all ref fields resolve to existing resources
925
+ 2. `ToolsAdmitted` — AgentToolProfile found (if referenced)
926
+ 3. `McpHealthy` — all referenced MCP servers exist
927
+ 4. `SkillsValidated` — all skills have valid format and sourceRef
928
+ 5. `SubagentsValid` — all subagents have non-empty taskKinds
929
+ 6. `ContextLabelsValid` — all context labels exist
930
+ 7. `RuntimeIdentityReady` — AgentServiceAccount exists
931
+ 8. `RolesAdmitted` — AgentRoleBinding requirements met
932
+ 9. `SecretsAdmitted` — AgentSecretGrant requirements met
933
+ 10. `ConfigAdmitted` — AgentConfigGrant requirements met
934
+ 11. `Ready` — all above conditions are True
935
+
936
+ **MCP Health Check (`checkMcpHealth()`):**
937
+ - HTTP GET to MCP server endpoint with 3-second timeout
938
+ - Returns: `{ serverName, status: 'healthy'|'unhealthy'|'unknown', latencyMs }`
939
+
940
+ **Capabilities List (`listStackCapabilities()`):**
941
+ - Returns array of `{ kind, name, status, ref }` for all referenced capabilities
942
+
943
+ **Relationships:**
944
+ - Belongs to: Organization
945
+ - References: AgentSubagent, AgentToolProfile, AgentMcpServer, AgentSkill,
946
+ AgentContextLabel, KrateWorkspacePolicy, AgentServiceAccount
947
+ - Referenced by: AgentDispatchRun, AgentTriggerRule, KrateProject
948
+ - Controls: which tools, prompts, and resources an agent session has access to
949
+
950
+ ---
951
+
952
+ #### AgentSubagent
953
+
954
+ | Field | Value |
955
+ |-------|-------|
956
+ | Storage | etcd |
957
+ | Context | agents |
958
+ | Plural | agentsubagents |
959
+ | Namespace | org-scoped |
960
+
961
+ **Purpose:** Named child-agent definition with role prompt, task kinds, tool subset,
962
+ and workspace scope. Enables hierarchical agent composition where a parent stack
963
+ can delegate to specialized subagents.
964
+
965
+ **Required Spec Fields:**
966
+ - `organizationRef` — owning organization
967
+ - `rolePrompt` — system prompt defining the subagent's role
968
+ - `taskKinds` — array of task kinds this subagent handles
969
+
970
+ **Optional Spec Fields:**
971
+ - `toolSubset[]` — restricted tool list (subset of parent)
972
+ - `workspaceScope` — workspace access level
973
+ - `maxConcurrency` — max parallel executions
974
+
975
+ **Validation:**
976
+ - `taskKinds` must be a non-empty array (checked during stack reconciliation)
977
+
978
+ **Relationships:**
979
+ - Belongs to: Organization
980
+ - Referenced by: AgentStack (via subagentRefs)
981
+ - Used during: dispatch to determine which subagent handles a task
982
+
983
+ ---
984
+
985
+ #### AgentToolProfile
986
+
987
+ | Field | Value |
988
+ |-------|-------|
989
+ | Storage | etcd |
990
+ | Context | agents |
991
+ | Plural | agenttoolprofiles |
992
+ | Namespace | org-scoped |
993
+
994
+ **Purpose:** Native tool policy defining filesystem access, network access, shell
995
+ access, and per-tool approval gates. Controls what an agent is allowed to do.
996
+
997
+ **Required Spec Fields:**
998
+ - `organizationRef` — owning organization
999
+ - `filesystemPolicy` — filesystem access rules
1000
+ - `approvalPolicyByTool` — map of tool names to approval requirements
1001
+
1002
+ **Optional Spec Fields:**
1003
+ - `networkPolicy` — network access rules
1004
+ - `shellPolicy` — shell command execution rules
1005
+ - `denyList[]` — explicitly denied tool/operation patterns
1006
+
1007
+ **Relationships:**
1008
+ - Belongs to: Organization
1009
+ - Referenced by: AgentStack (via toolPolicy/toolPolicyRef)
1010
+ - Evaluated during: agent execution for tool-use gating
1011
+
1012
+ ---
1013
+
1014
+ #### AgentMcpServer
1015
+
1016
+ | Field | Value |
1017
+ |-------|-------|
1018
+ | Storage | etcd |
1019
+ | Context | agents |
1020
+ | Plural | agentmcpservers |
1021
+ | Namespace | org-scoped |
1022
+
1023
+ **Purpose:** Managed MCP (Model Context Protocol) endpoint registration with transport
1024
+ type, discovery metadata, health check configuration, and secret/config references.
1025
+
1026
+ **Required Spec Fields:**
1027
+ - `organizationRef` — owning organization
1028
+ - `transport` — transport type (e.g., 'stdio', 'http', 'sse')
1029
+ - `scope` — access scope
1030
+
1031
+ **Optional Spec Fields:**
1032
+ - `endpoint` — HTTP endpoint URL (for health checks)
1033
+ - `discoveryUrl` — MCP discovery URL
1034
+ - `secretRefs[]` — references to Kubernetes Secrets
1035
+ - `configRefs[]` — references to ConfigMaps
1036
+ - `healthCheck` — health check configuration
1037
+
1038
+ **Health Monitoring:**
1039
+ - `checkMcpHealth()` in stack controller performs HTTP GET with 3s timeout
1040
+ - Status: `healthy` (response.ok), `unhealthy` (error/timeout), `unknown` (no endpoint)
1041
+
1042
+ **Relationships:**
1043
+ - Belongs to: Organization
1044
+ - Referenced by: AgentStack (via mcpServerRefs)
1045
+ - Provides: tools and resources to agent sessions via MCP protocol
1046
+
1047
+ ---
1048
+
1049
+ #### AgentSkill
1050
+
1051
+ | Field | Value |
1052
+ |-------|-------|
1053
+ | Storage | etcd |
1054
+ | Context | agents |
1055
+ | Plural | agentskills |
1056
+ | Namespace | org-scoped |
1057
+
1058
+ **Purpose:** Reusable runbook/procedure bundle with prompt fragments, tool dependencies,
1059
+ and output contracts. Skills encapsulate specialized knowledge and procedures.
1060
+
1061
+ **Required Spec Fields:**
1062
+ - `organizationRef` — owning organization
1063
+ - `format` — skill format (e.g., 'markdown', 'yaml')
1064
+ - `sourceRef` — reference to the skill source (git path, URL, etc.)
1065
+
1066
+ **Optional Spec Fields:**
1067
+ - `promptFragments[]` — prompt fragments contributed by this skill
1068
+ - `toolDeps[]` — required tools
1069
+ - `outputContract` — expected output schema
1070
+
1071
+ **Validation:**
1072
+ - Must have non-empty `format` and `sourceRef` (checked during stack reconciliation)
1073
+
1074
+ **Relationships:**
1075
+ - Belongs to: Organization
1076
+ - Referenced by: AgentStack (via skillRefs)
1077
+ - Injected into: agent context during session initialization
1078
+
1079
+ ---
1080
+
1081
+ #### AgentTriggerRule
1082
+
1083
+ | Field | Value |
1084
+ |-------|-------|
1085
+ | Storage | etcd |
1086
+ | Context | agents |
1087
+ | Plural | agenttriggerrules |
1088
+ | Namespace | org-scoped |
1089
+
1090
+ **Purpose:** Event-to-stack routing rules for CI failures, webhooks, comments, labels,
1091
+ schedules (cron), and manual dispatch. Determines which events should trigger which
1092
+ agent stacks.
1093
+
1094
+ **Required Spec Fields:**
1095
+ - `organizationRef` — owning organization
1096
+ - `sources` — event source types (e.g., ['push', 'pull_request', 'issue_comment'])
1097
+ - `agentStack` — reference to AgentStack to dispatch
1098
+ - `taskKind` — task kind for the dispatch
1099
+
1100
+ **Optional Spec Fields:**
1101
+ - `enabled` — boolean toggle (default: true)
1102
+ - `repository` — scope to specific repository
1103
+ - `allowedActors[]` — restrict triggering to specific users
1104
+ - `cronExpression` — 5-field cron for scheduled triggers
1105
+ - `webhookTrigger` — `{ url, events[], repository, action, secretRef }`
1106
+ - `commentTrigger` — `{ pattern, repos[] }`
1107
+ - `labelTrigger` — `{ labels[], action: 'labeled'|'unlabeled' }`
1108
+
1109
+ **Source Type Detection (`getTriggerSourceType()`):**
1110
+ - `cronExpression` present → `cron`
1111
+ - `webhookTrigger` present → `webhook`
1112
+ - `commentTrigger` present → `comment`
1113
+ - `labelTrigger` present → `label`
1114
+ - `sources` present → `event`
1115
+ - None → `unknown`
1116
+
1117
+ **Validation (`validateTriggerRule()`):**
1118
+ - Validates source-specific sub-configs (cron expression, webhook URL, etc.)
1119
+ - Cron: 5 fields, valid characters only
1120
+ - Webhook: valid HTTP/HTTPS URL
1121
+ - Comment: non-empty pattern string
1122
+ - Label: non-empty labels array, valid action
1123
+
1124
+ **Cron Scheduling:**
1125
+ - `validateCronExpression()` — validates 5-field cron syntax
1126
+ - `calculateNextRun()` — computes next execution time (minute precision)
1127
+
1128
+ **Event Evaluation (`evaluateEvent()`):**
1129
+ - Matches event type against rule sources
1130
+ - Checks repository scope filter
1131
+ - Checks actor filter (allowedActors)
1132
+ - Deduplication: skips if identical execution already exists (non-Failed)
1133
+
1134
+ **Webhook Event Evaluation (`evaluateWebhookEvent()`):**
1135
+ - Checks enabled flag
1136
+ - Matches eventType against webhookTrigger.events (or wildcard '*')
1137
+ - Filters by webhookTrigger.repository
1138
+ - Filters by webhookTrigger.action
1139
+ - Deduplicates by rule name
1140
+ - Returns matchingRules + dispatchIntents
1141
+
1142
+ **Relationships:**
1143
+ - Belongs to: Organization
1144
+ - References: AgentStack (dispatch target)
1145
+ - Produces: AgentTriggerExecution (evaluation record), AgentDispatchRun (on match)
1146
+ - Consumed by: webhook controller, event bus, cron scheduler
1147
+
1148
+ ---
1149
+
1150
+ #### AgentContextLabel
1151
+
1152
+ | Field | Value |
1153
+ |-------|-------|
1154
+ | Storage | etcd |
1155
+ | Context | agents |
1156
+ | Plural | agentcontextlabels |
1157
+ | Namespace | org-scoped |
1158
+
1159
+ **Purpose:** Reviewed prompt fragment with provenance tracking and allowlisted sources.
1160
+ Provides curated context that can be injected into agent sessions.
1161
+
1162
+ **Required Spec Fields:**
1163
+ - `organizationRef` — owning organization
1164
+ - `promptFragment` — the prompt text to inject
1165
+ - `allowedSources` — which stacks/contexts may use this label
1166
+
1167
+ **Relationships:**
1168
+ - Belongs to: Organization
1169
+ - Referenced by: AgentStack (via contextLabelRefs)
1170
+ - Injected into: AgentContextBundle during dispatch
1171
+
1172
+ ---
1173
+
1174
+ #### KrateWorkspacePolicy
1175
+
1176
+ | Field | Value |
1177
+ |-------|-------|
1178
+ | Storage | etcd |
1179
+ | Context | agents |
1180
+ | Plural | krateworkspacepolicies |
1181
+ | Namespace | org-scoped |
1182
+
1183
+ **Purpose:** Git worktree provisioning, cleanup, retention, and trust tier policies.
1184
+ Controls how agent workspaces are created, reused, and cleaned up.
1185
+
1186
+ **Required Spec Fields:**
1187
+ - `organizationRef` — owning organization
1188
+ - `mode` — provisioning mode (e.g., 'on-demand', 'pre-warmed')
1189
+ - `retentionPolicy` — `{ maxAge, maxCount, cleanupSchedule }`
1190
+
1191
+ **Optional Spec Fields:**
1192
+ - `trustTier` — workspace trust level
1193
+ - `storageClassName` — PVC storage class
1194
+ - `defaultCapacity` — default PVC size
1195
+
1196
+ **Relationships:**
1197
+ - Belongs to: Organization
1198
+ - Referenced by: AgentStack (workspace policy)
1199
+ - Controls: KrateWorkspace creation and lifecycle
1200
+
1201
+ ---
1202
+
1203
+ #### AgentAdapter
1204
+
1205
+ | Field | Value |
1206
+ |-------|-------|
1207
+ | Storage | etcd |
1208
+ | Context | agents |
1209
+ | Plural | agentadapters |
1210
+ | Namespace | org-scoped |
1211
+
1212
+ **Purpose:** Agent adapter definition declaring transport type, capabilities matrix,
1213
+ authentication requirements, and installation method. Adapters are the bridge between
1214
+ the Krate control plane and actual agent runtimes (e.g., Claude Code, OpenAI, etc.).
1215
+
1216
+ **Required Spec Fields:**
1217
+ - `organizationRef` — owning organization
1218
+ - `adapterType` — adapter identifier (e.g., 'claude-code', 'openai-assistants')
1219
+ - `transport` — communication transport (e.g., 'http', 'stdio', 'websocket')
1220
+
1221
+ **Optional Spec Fields:**
1222
+ - `capabilities[]` — supported operations/features
1223
+ - `authRequirements` — what credentials the adapter needs
1224
+ - `installationMethod` — how to deploy the adapter
1225
+
1226
+ **Relationships:**
1227
+ - Belongs to: Organization
1228
+ - Referenced by: AgentTransportBinding (connection config)
1229
+ - Used by: AgentStack (via adapter field)
1230
+
1231
+ ---
1232
+
1233
+ #### AgentTransportBinding
1234
+
1235
+ | Field | Value |
1236
+ |-------|-------|
1237
+ | Storage | etcd |
1238
+ | Context | agents |
1239
+ | Plural | agenttransportbindings |
1240
+ | Namespace | org-scoped |
1241
+
1242
+ **Purpose:** Connection configuration for an adapter instance with endpoint, protocol
1243
+ details, authentication, health check, and reconnect policy.
1244
+
1245
+ **Required Spec Fields:**
1246
+ - `organizationRef` — owning organization
1247
+ - `adapterRef` — reference to AgentAdapter
1248
+ - `endpoint` — connection endpoint URL
1249
+ - `protocol` — protocol version/details
1250
+
1251
+ **Optional Spec Fields:**
1252
+ - `auth` — authentication configuration (token refs, etc.)
1253
+ - `healthCheck` — `{ interval, timeout, path }`
1254
+ - `reconnectPolicy` — `{ maxRetries, backoffMs }`
1255
+ - `tls` — TLS configuration
1256
+
1257
+ **Relationships:**
1258
+ - Belongs to: Organization
1259
+ - References: AgentAdapter
1260
+ - Used by: Agent Mux client for session establishment
1261
+
1262
+ ---
1263
+
1264
+ #### AgentProviderConfig
1265
+
1266
+ | Field | Value |
1267
+ |-------|-------|
1268
+ | Storage | etcd |
1269
+ | Context | agents |
1270
+ | Plural | agentproviderconfigs |
1271
+ | Namespace | org-scoped |
1272
+
1273
+ **Purpose:** Model provider configuration with API base URL, authentication type,
1274
+ default model, model translation mappings, and rate limits.
1275
+
1276
+ **Required Spec Fields:**
1277
+ - `organizationRef` — owning organization
1278
+ - `provider` — provider identifier (e.g., 'anthropic', 'openai')
1279
+ - `authType` — authentication method (e.g., 'api-key', 'oauth')
1280
+
1281
+ **Optional Spec Fields:**
1282
+ - `apiBase` — API base URL override
1283
+ - `defaultModel` — default model to use
1284
+ - `modelTranslations` — map of model aliases to actual model IDs
1285
+ - `rateLimits` — `{ requestsPerMinute, tokensPerMinute }`
1286
+ - `secretRef` — reference to K8s Secret containing API key
1287
+
1288
+ **Relationships:**
1289
+ - Belongs to: Organization
1290
+ - Referenced by: AgentStack (provider configuration)
1291
+ - Used by: Agent Mux for model API calls
1292
+
1293
+ ---
1294
+
1295
+ #### AgentGatewayConfig
1296
+
1297
+ | Field | Value |
1298
+ |-------|-------|
1299
+ | Storage | etcd |
1300
+ | Context | agents |
1301
+ | Plural | agentgatewayconfigs |
1302
+ | Namespace | org-scoped |
1303
+
1304
+ **Purpose:** Runtime Agent Mux gateway connection settings with URL, auth configuration,
1305
+ reconnect policy, and feature flags.
1306
+
1307
+ **Required Spec Fields:**
1308
+ - `organizationRef` — owning organization
1309
+ - `gatewayUrl` — Agent Mux gateway URL
1310
+
1311
+ **Optional Spec Fields:**
1312
+ - `auth` — gateway authentication config
1313
+ - `reconnectPolicy` — `{ maxRetries, backoffMs }`
1314
+ - `featureFlags` — feature toggles for the gateway
1315
+ - `sseEnabled` — enable SSE event streaming
1316
+
1317
+ **Relationships:**
1318
+ - Belongs to: Organization
1319
+ - Used by: Agent Mux client (`createAgentMuxClient()`)
1320
+ - Controls: how the dispatch controller connects to the agent runtime
1321
+
1322
+ ---
1323
+
1324
+ #### KrateProject
1325
+
1326
+ | Field | Value |
1327
+ |-------|-------|
1328
+ | Storage | etcd |
1329
+ | Context | agents |
1330
+ | Plural | krateprojects |
1331
+ | Namespace | org-scoped |
1332
+
1333
+ **Purpose:** Organization project grouping issues, linked repositories, kanban board
1334
+ configuration, default workflow, and backend sync references.
1335
+
1336
+ **Required Spec Fields:**
1337
+ - `organizationRef` — owning organization
1338
+ - `displayName` — project name
1339
+
1340
+ **Optional Spec Fields:**
1341
+ - `workflowColumns[]` — kanban column definitions: `[{ id, displayName, color, default? }]`
1342
+ - `boardState` — `active` or `archived`
1343
+ - `repositoryRefs[]` — linked repositories
1344
+ - `stackRefs[]` — linked agent stacks
1345
+ - `syncRefs[]` — external project sync references (GitHub Projects, etc.)
1346
+
1347
+ **Validation (`validateAgentProject()`):**
1348
+ - metadata.name required
1349
+ - organizationRef required
1350
+ - workflowColumns: non-empty array, no duplicate column IDs
1351
+ - boardState: must be `active` or `archived` (if set)
1352
+
1353
+ **Board Operations:**
1354
+ - `getWorkflowColumns()` — returns ordered column array
1355
+ - `getDefaultColumn()` — column with `default: true`, or first column
1356
+ - `getBoardState()` — returns current board state (default: 'active')
1357
+
1358
+ **Issue Assignment:**
1359
+ - Issues reference projects via `spec.projectRefs`
1360
+ - Issues have `workflowState` field corresponding to column IDs
1361
+ - Kanban drag-drop updates issue workflowState
1362
+
1363
+ **Relationships:**
1364
+ - Belongs to: Organization
1365
+ - Groups: Issues (via projectRefs on issues)
1366
+ - References: Repositories (repositoryRefs), AgentStacks (stackRefs)
1367
+ - Synced to: GitHub Projects (via ExternalBackendBinding)
1368
+
1369
+ ---
1370
+
1371
+ #### AgentRoleBinding
1372
+
1373
+ | Field | Value |
1374
+ |-------|-------|
1375
+ | Storage | etcd |
1376
+ | Context | identity |
1377
+ | Plural | agentrolebindings |
1378
+ | Namespace | org-scoped |
1379
+
1380
+ **Purpose:** Managed projection to native Kubernetes RBAC for agent identity. Maps
1381
+ agent service accounts to cluster roles and namespaced roles.
1382
+
1383
+ **Required Spec Fields:**
1384
+ - `organizationRef` — owning organization
1385
+ - `subject` — AgentServiceAccount or user reference
1386
+ - `roleRef` — Kubernetes Role or ClusterRole reference
1387
+ - `scope` — namespace or cluster scope
1388
+
1389
+ **Relationships:**
1390
+ - Belongs to: Organization
1391
+ - References: AgentServiceAccount (subject)
1392
+ - Produces: Kubernetes RoleBinding/ClusterRoleBinding
1393
+ - Checked by: stack reconciliation (RolesAdmitted condition)
1394
+
1395
+ ---
1396
+
1397
+ #### AgentSecretGrant
1398
+
1399
+ | Field | Value |
1400
+ |-------|-------|
1401
+ | Storage | etcd |
1402
+ | Context | identity |
1403
+ | Plural | agentsecretgrants |
1404
+ | Namespace | org-scoped |
1405
+
1406
+ **Purpose:** Explicit permission for a subject to access specific Secret keys with
1407
+ purpose scoping. Provides fine-grained secret access control for agents.
1408
+
1409
+ **Required Spec Fields:**
1410
+ - `organizationRef` — owning organization
1411
+ - `subject` — who gets access (AgentServiceAccount ref)
1412
+ - `secretRef` — K8s Secret reference `{ name, namespace, keys[] }`
1413
+ - `purpose` — why access is needed
1414
+
1415
+ **Relationships:**
1416
+ - Belongs to: Organization
1417
+ - Grants access to: Kubernetes Secrets
1418
+ - Checked by: stack reconciliation (SecretsAdmitted condition)
1419
+ - Checked by: permission reviewer during dispatch
1420
+
1421
+ ---
1422
+
1423
+ #### AgentConfigGrant
1424
+
1425
+ | Field | Value |
1426
+ |-------|-------|
1427
+ | Storage | etcd |
1428
+ | Context | identity |
1429
+ | Plural | agentconfiggrants |
1430
+ | Namespace | org-scoped |
1431
+
1432
+ **Purpose:** Explicit permission for a subject to access specific ConfigMap keys with
1433
+ purpose scoping.
1434
+
1435
+ **Required Spec Fields:**
1436
+ - `organizationRef` — owning organization
1437
+ - `subject` — who gets access
1438
+ - `configMapRef` — K8s ConfigMap reference `{ name, namespace, keys[] }`
1439
+ - `purpose` — why access is needed
1440
+
1441
+ **Relationships:**
1442
+ - Belongs to: Organization
1443
+ - Grants access to: Kubernetes ConfigMaps
1444
+ - Checked by: stack reconciliation (ConfigAdmitted condition)
1445
+
1446
+ ---
1447
+
1448
+ #### AgentDispatchRun
1449
+
1450
+ | Field | Value |
1451
+ |-------|-------|
1452
+ | Storage | postgres |
1453
+ | Context | agents |
1454
+ | Plural | agentdispatchruns |
1455
+ | Namespace | org-scoped |
1456
+
1457
+ **Purpose:** Logical CI-like run record visible alongside Pipeline/Job records. Represents
1458
+ a complete agent invocation with queue state, execution status, workspace binding,
1459
+ cost tracking, and memory snapshot reference.
1460
+
1461
+ **Required Spec Fields:**
1462
+ - `organizationRef` — owning organization
1463
+ - `repository` — target repository
1464
+ - `sourceRefs` — source references that triggered this run
1465
+ - `agentStack` — reference to AgentStack used
1466
+ - `taskKind` — the type of task (e.g., 'diagnostic', 'fix', 'review')
1467
+
1468
+ **Optional Spec Fields:**
1469
+ - `contextBundleRef` — reference to AgentContextBundle
1470
+ - `memorySnapshotRef` — reference to AgentMemorySnapshot
1471
+ - `workspaceRef` — reference to KrateWorkspace
1472
+ - `mountSpec` — volume/volumeMount configuration
1473
+
1474
+ **Lifecycle Phases:**
1475
+ - `Pending` — created, workspace being provisioned
1476
+ - `AwaitingApproval` — permission review requires approval
1477
+ - `Queued` — ready but Agent Mux not available
1478
+ - `Running` — session launched, executing
1479
+ - `Succeeded` — completed successfully
1480
+ - `Failed` — execution failed
1481
+ - `Cancelled` — manually cancelled
1482
+
1483
+ **Status Fields:**
1484
+ - `queuedAt` — when run entered queue
1485
+ - `sseSubscription` — `{ runId, active }` event stream state
1486
+ - `transcriptRef` — reference to AgentSessionTranscript
1487
+ - `conditions[]` — K8s-style condition array
1488
+
1489
+ **Creation Flow (via `createManualDispatch()`):**
1490
+ 1. Find AgentStack by name
1491
+ 2. Permission review (allowed / denied / requires-approval)
1492
+ 3. Memory snapshot creation (if AgentMemoryRepository exists)
1493
+ 4. Approval gate (if required)
1494
+ 5. Workspace provisioning (reuse existing or create new)
1495
+ 6. Context bundle assembly
1496
+ 7. Create AgentDispatchRun + AgentDispatchAttempt resources
1497
+ 8. Launch Agent Mux session
1498
+ 9. Start SSE subscription
1499
+ 10. Reconcile transcript
1500
+
1501
+ **Relationships:**
1502
+ - Belongs to: Organization
1503
+ - References: AgentStack, Repository, KrateWorkspace, AgentContextBundle
1504
+ - Has: AgentDispatchAttempt (1:N), AgentSession (1:1), AgentApproval (0:N)
1505
+ - Produces: KrateArtifact (0:N), AgentSessionTranscript
1506
+ - Linked via: WorkItemSessionLink, WorkItemWorkspaceLink
1507
+
1508
+ ---
1509
+
1510
+ #### AgentDispatchAttempt
1511
+
1512
+ | Field | Value |
1513
+ |-------|-------|
1514
+ | Storage | postgres |
1515
+ | Context | agents |
1516
+ | Plural | agentdispatchattempts |
1517
+ | Namespace | org-scoped |
1518
+
1519
+ **Purpose:** Concrete execution attempt with reason, stack snapshot, runtime state,
1520
+ and Agent Mux binding. One run may have multiple attempts (retries).
1521
+
1522
+ **Required Spec Fields:**
1523
+ - `organizationRef` — owning organization
1524
+ - `agentDispatchRun` — reference to parent run
1525
+ - `attemptReason` — why this attempt was created (e.g., 'initial', 'retry')
1526
+ - `agentStackSnapshot` — frozen copy of the stack spec at attempt time
1527
+
1528
+ **Optional Spec Fields:**
1529
+ - `contextBundleDigest` — digest of the context bundle used
1530
+
1531
+ **Status Fields:**
1532
+ - `permissionSnapshot` — captured permission review result
1533
+ - `queueEnteredAt` — when attempt entered queue
1534
+ - `agentMuxRunId` — Agent Mux run identifier
1535
+ - `agentMuxSessionId` — Agent Mux session identifier
1536
+ - `startedAt` — when execution began
1537
+
1538
+ **Relationships:**
1539
+ - Belongs to: AgentDispatchRun (parent)
1540
+ - Creates: AgentSession (1:1 per successful launch)
1541
+ - Contains: frozen stack snapshot for reproducibility
1542
+
1543
+ ---
1544
+
1545
+ #### AgentSession
1546
+
1547
+ | Field | Value |
1548
+ |-------|-------|
1549
+ | Storage | postgres |
1550
+ | Context | agents |
1551
+ | Plural | agentsessions |
1552
+ | Namespace | org-scoped |
1553
+
1554
+ **Purpose:** Krate projection of an Agent Mux chat/session with lifecycle state.
1555
+ Represents a live or completed interaction between an agent and the system.
1556
+
1557
+ **Required Spec Fields:**
1558
+ - `organizationRef` — owning organization
1559
+ - `agentMuxSessionId` �� external Agent Mux session identifier
1560
+ - `dispatchRun` — reference to the parent AgentDispatchRun
1561
+
1562
+ **Lifecycle:**
1563
+ - `Active` — session is live, agent is executing
1564
+ - `Completed` — session ended normally
1565
+ - `Failed` — session ended with error
1566
+ - `Cancelled` — session was manually terminated
1567
+
1568
+ **Relationships:**
1569
+ - Belongs to: AgentDispatchRun
1570
+ - Has: AgentSessionTranscript, AgentSessionAttachment
1571
+ - Linked via: WorkItemSessionLink (to Issues/PRs)
1572
+ - Bound to: KrateWorkspace (via workspace controller)
1573
+
1574
+ ---
1575
+
1576
+ #### AgentContextBundle
1577
+
1578
+ | Field | Value |
1579
+ |-------|-------|
1580
+ | Storage | postgres |
1581
+ | Context | agents |
1582
+ | Plural | agentcontextbundles |
1583
+ | Namespace | org-scoped |
1584
+
1585
+ **Purpose:** Immutable prompt/context snapshot with content-addressable digest,
1586
+ provenance tracking, source references, and redaction manifest.
1587
+
1588
+ **Required Spec Fields:**
1589
+ - `organizationRef` — owning organization
1590
+ - `dispatchRun` — reference to the run that created this bundle
1591
+ - `digest` — content-addressable hash of the bundle
1592
+ - `sources` — array of source references (context labels, prompts, etc.)
1593
+
1594
+ **Assembly (via `assembleContextBundle()`):**
1595
+ - Gathers: stack prompts, context labels, repository context, source refs
1596
+ - Applies: redaction scanning for secrets/credentials
1597
+ - Produces: immutable snapshot with digest for cache/dedup
1598
+
1599
+ **Relationships:**
1600
+ - Belongs to: AgentDispatchRun
1601
+ - References: AgentContextLabel (sources)
1602
+ - Contains: assembled prompt content for the agent session
1603
+
1604
+ ---
1605
+
1606
+ #### KrateArtifact
1607
+
1608
+ | Field | Value |
1609
+ |-------|-------|
1610
+ | Storage | postgres |
1611
+ | Context | agents |
1612
+ | Plural | krateartifacts |
1613
+ | Namespace | org-scoped |
1614
+
1615
+ **Purpose:** Durable agent output with kind classification, content-addressable digest,
1616
+ and retention policy. Captures the results of agent work.
1617
+
1618
+ **Required Spec Fields:**
1619
+ - `organizationRef` — owning organization
1620
+ - `dispatchRun` — reference to producing run
1621
+ - `kind` — artifact type (e.g., 'patch', 'report', 'review', 'log')
1622
+ - `digest` — content-addressable hash
1623
+
1624
+ **Optional Spec Fields:**
1625
+ - `retentionPolicy` — how long to keep the artifact
1626
+ - `size` — artifact size in bytes
1627
+ - `mimeType` — content type
1628
+
1629
+ **Relationships:**
1630
+ - Belongs to: AgentDispatchRun (producer)
1631
+ - Stored in: object storage (referenced by digest)
1632
+ - Linked to: Pipeline jobs (for CI artifacts)
1633
+
1634
+ ---
1635
+
1636
+ #### AgentApproval
1637
+
1638
+ | Field | Value |
1639
+ |-------|-------|
1640
+ | Storage | postgres |
1641
+ | Context | agents |
1642
+ | Plural | agentapprovals |
1643
+ | Namespace | org-scoped |
1644
+
1645
+ **Purpose:** Human gate for tools, secrets, write-back, and release actions. Implements
1646
+ the approval workflow that pauses agent execution until a human decides.
1647
+
1648
+ **Required Spec Fields:**
1649
+ - `organizationRef` — owning organization
1650
+ - `dispatchRun` — reference to the run requesting approval
1651
+ - `action` — what is being requested: `tool-use`, `secret-access`, `write-back`, `release`, `escalation`
1652
+ - `requestedBy` — who/what triggered the request
1653
+
1654
+ **Optional Spec Fields:**
1655
+ - `description` — context about why approval is needed
1656
+ - `requestedAt` — ISO timestamp
1657
+
1658
+ **Lifecycle Phases:**
1659
+ - `Pending` — awaiting human decision
1660
+ - `Approved` — action allowed to proceed
1661
+ - `Denied` — action blocked
1662
+
1663
+ **Operations:**
1664
+ - `createApprovalRequest()` — creates with dedup check (no duplicate pending for same run+action)
1665
+ - `recordDecision()` — records approve/deny with decidedBy and reason
1666
+ - `isActionApproved()` — checks if action is approved for a run
1667
+ - `enforceApproval()` — enforcement gate (allowed/denied/pending)
1668
+ - `persistApproval()` — persists via applyResource
1669
+ - `listPendingApprovals()` — finds all pending approvals for an org
1670
+ - `listApprovalsForRun()` — finds all approvals for a dispatch run
1671
+
1672
+ **Relationships:**
1673
+ - Belongs to: AgentDispatchRun
1674
+ - Blocks: agent execution until decided
1675
+ - Created by: dispatch controller (on requires-approval review)
1676
+ - Decided by: human operator via UI or API
1677
+
1678
+ ---
1679
+
1680
+ #### AgentTriggerExecution
1681
+
1682
+ | Field | Value |
1683
+ |-------|-------|
1684
+ | Storage | postgres |
1685
+ | Context | agents |
1686
+ | Plural | agenttriggerexecutions |
1687
+ | Namespace | org-scoped |
1688
+
1689
+ **Purpose:** Durable trigger evaluation record with deduplication state, coalescing
1690
+ decisions, and rejection reasons. Provides audit trail for trigger matching.
1691
+
1692
+ **Required Spec Fields:**
1693
+ - `organizationRef` — owning organization
1694
+ - `triggerRule` — reference to the evaluated AgentTriggerRule
1695
+ - `sourceEvent` — event UID (`type:kind:name`)
1696
+ - `decision` — evaluation outcome
1697
+
1698
+ **Decision Values:**
1699
+ - `Dispatching` / `Dispatched` — trigger matched, run created
1700
+ - `Skipped` — trigger did not match (with reason)
1701
+ - `Deduplicated` — would match but identical execution already exists
1702
+ - `Failed` — dispatch attempted but failed
1703
+
1704
+ **Status Fields:**
1705
+ - `phase` — mirrors decision
1706
+ - `reason` — explanation of the decision
1707
+ - `evaluatedAt` — when evaluation occurred
1708
+ - `dispatchRunRef` — reference to created run (if dispatched)
1709
+
1710
+ **Relationships:**
1711
+ - References: AgentTriggerRule (the rule that was evaluated)
1712
+ - Produces: AgentDispatchRun (on successful dispatch)
1713
+ - Used for: deduplication of subsequent identical events
1714
+
1715
+ ---
1716
+
1717
+ #### AgentCapabilityRequirement
1718
+
1719
+ | Field | Value |
1720
+ |-------|-------|
1721
+ | Storage | postgres |
1722
+ | Context | agents |
1723
+ | Plural | agentcapabilityrequirements |
1724
+ | Namespace | org-scoped |
1725
+
1726
+ **Purpose:** Computed dependency record derived from tools, MCP servers, skills, models,
1727
+ and subagents. Represents what capabilities an agent needs to function.
1728
+
1729
+ **Required Spec Fields:**
1730
+ - `organizationRef` — owning organization
1731
+ - `ownerRef` — reference to the requiring resource (usually AgentStack)
1732
+ - `requiredRoles` — array of required role/capability identifiers
1733
+
1734
+ **Relationships:**
1735
+ - Belongs to: AgentStack (owner)
1736
+ - Checked during: stack reconciliation and dispatch permission review
1737
+
1738
+ ---
1739
+
1740
+ #### WorkItemSessionLink
1741
+
1742
+ | Field | Value |
1743
+ |-------|-------|
1744
+ | Storage | postgres |
1745
+ | Context | agents |
1746
+ | Plural | workitemsessionlinks |
1747
+ | Namespace | org-scoped |
1748
+
1749
+ **Purpose:** Association between issues/PRs and agent sessions. Tracks which agent
1750
+ sessions worked on which work items.
1751
+
1752
+ **Required Spec Fields:**
1753
+ - `organizationRef` — owning organization
1754
+ - `workItemRef` — reference to Issue or PullRequest
1755
+ - `agentSession` — reference to AgentSession
1756
+
1757
+ **Creation:**
1758
+ - `linkWorkItemToSession()` in workspace controller
1759
+ - Auto-generated name: `wisl-{sessionRef}-{workItemRef}-{timestamp}`
1760
+
1761
+ **Relationships:**
1762
+ - Links: Issue/PullRequest ↔ AgentSession
1763
+ - Used by: project boards to show which sessions worked on which items
1764
+ - Enables: session history view per work item
1765
+
1766
+ ---
1767
+
1768
+ #### WorkItemWorkspaceLink
1769
+
1770
+ | Field | Value |
1771
+ |-------|-------|
1772
+ | Storage | postgres |
1773
+ | Context | agents |
1774
+ | Plural | workitemworkspacelinks |
1775
+ | Namespace | org-scoped |
1776
+
1777
+ **Purpose:** Association between issues/PRs and agent workspaces. Tracks which
1778
+ workspaces contain work related to which items.
1779
+
1780
+ **Required Spec Fields:**
1781
+ - `organizationRef` — owning organization
1782
+ - `workItemRef` — reference to Issue or PullRequest
1783
+ - `workspace` — reference to KrateWorkspace
1784
+
1785
+ **Optional Spec Fields:**
1786
+ - `workItemKind` — `Issue` or `PullRequest`
1787
+
1788
+ **Creation:**
1789
+ - `linkWorkItem()` in workspace controller
1790
+ - Auto-generated name: `wiwl-{workspaceName}-{workItemRef}-{timestamp}`
1791
+
1792
+ **Relationships:**
1793
+ - Links: Issue/PullRequest ↔ KrateWorkspace
1794
+ - Used by: workspace controller for run history queries
1795
+
1796
+ ---
1797
+
1798
+ #### AgentSessionTranscript
1799
+
1800
+ | Field | Value |
1801
+ |-------|-------|
1802
+ | Storage | postgres |
1803
+ | Context | agents |
1804
+ | Plural | agentsessiontranscripts |
1805
+ | Namespace | org-scoped |
1806
+
1807
+ **Purpose:** Durable chat transcript with message nodes, pagination support, and
1808
+ cost-per-turn tracking. Stores the full conversation history.
1809
+
1810
+ **Required Spec Fields:**
1811
+ - `organizationRef` — owning organization
1812
+ - `sessionRef` — reference to AgentSession
1813
+ - `messages` — array of message nodes
1814
+
1815
+ **Message Node Structure:**
1816
+ - `role` — user/assistant/system/tool
1817
+ - `content` — message content
1818
+ - `timestamp` — when the message was sent
1819
+ - `costTokens` — token usage for this turn
1820
+ - `toolCalls[]` — tool invocations (if any)
1821
+
1822
+ **Reconciliation:**
1823
+ - Created by `agentMuxClient.reconcileTranscript()`
1824
+ - Updated as SSE events stream in from Agent Mux
1825
+ - Referenced by AgentDispatchRun.status.transcriptRef
1826
+
1827
+ **Relationships:**
1828
+ - Belongs to: AgentSession
1829
+ - Contains: full conversation history
1830
+ - Updated via: SSE event streaming from Agent Mux
1831
+
1832
+ ---
1833
+
1834
+ #### AgentSessionAttachment
1835
+
1836
+ | Field | Value |
1837
+ |-------|-------|
1838
+ | Storage | postgres |
1839
+ | Context | agents |
1840
+ | Plural | agentsessionattachments |
1841
+ | Namespace | org-scoped |
1842
+
1843
+ **Purpose:** File attached to a session message with source type, MIME type, digest,
1844
+ and redaction status.
1845
+
1846
+ **Required Spec Fields:**
1847
+ - `organizationRef` — owning organization
1848
+ - `sessionRef` — reference to AgentSession
1849
+ - `sourceType` — where the file came from (e.g., 'workspace', 'upload', 'tool-output')
1850
+ - `digest` — content-addressable hash
1851
+
1852
+ **Optional Spec Fields:**
1853
+ - `mimeType` — MIME type
1854
+ - `size` — file size
1855
+ - `redacted` — boolean indicating if content was redacted
1856
+ - `originalPath` — original file path in workspace
1857
+
1858
+ **Relationships:**
1859
+ - Belongs to: AgentSession
1860
+ - Stored in: object storage (referenced by digest)
1861
+ - May reference: KrateWorkspace files
1862
+
1863
+ ---
1864
+
1865
+ ### 1.6 Workspace Domain (3 kinds)
1866
+
1867
+ ---
1868
+
1869
+ #### KrateWorkspace
1870
+
1871
+ | Field | Value |
1872
+ |-------|-------|
1873
+ | Storage | etcd |
1874
+ | Context | workspaces |
1875
+ | Plural | krateworkspaces |
1876
+ | Namespace | org-scoped |
1877
+
1878
+ **Purpose:** Volume-backed git workspace with PVC lifecycle, repository binding, runner
1879
+ mount spec, session associations, and run history.
1880
+
1881
+ **Required Spec Fields:**
1882
+ - `organizationRef` — owning organization
1883
+ - `repository` — bound git repository
1884
+ - `volumeSpec` — PVC specification `{ storageClassName, capacity, accessModes }`
1885
+
1886
+ **Optional Spec Fields:**
1887
+ - `branch` — checked-out branch (default: 'main')
1888
+ - `pvcName` — PVC name (auto-generated: `krate-ws-{name}`)
1889
+ - `associations[]` — `[{ kind, name, addedAt }]` linked resources
1890
+
1891
+ **Lifecycle Phases:**
1892
+ - `Pending` — workspace created, PVC not yet bound
1893
+ - `Provisioning` — PVC being created
1894
+ - `Ready` — workspace available for use
1895
+ - `InUse` — claimed by a dispatch run
1896
+ - `Released` — run completed, workspace available again
1897
+ - `Archived` — long-term storage, not active
1898
+ - `Terminating` — being deleted
1899
+
1900
+ **Operations:**
1901
+ - `createWorkspace()` — creates workspace + PVC manifest
1902
+ - `deleteWorkspace()` — marks Terminating, generates PVC delete manifest
1903
+ - `claimWorkspace()` — transitions Ready → InUse with runRef
1904
+ - `releaseWorkspace()` — transitions InUse → Ready
1905
+ - `archiveWorkspace()` — transitions to Archived
1906
+ - `recoverWorkspace()` — transitions Archived → Active
1907
+ - `findReusableWorkspace()` — finds Ready workspace matching org+repo+branch
1908
+ - `initializeWorkspace()` — generates git clone command spec
1909
+ - `checkoutBranch()` — generates git checkout command spec
1910
+ - `syncWorkspace()` — generates git fetch + reset command specs
1911
+ - `getMountSpec()` — generates PVC volume + volumeMount for pod specs
1912
+ - `bindSession()` — records session binding in status
1913
+ - `linkWorkItem()` — creates WorkItemWorkspaceLink
1914
+ - `linkWorkItemToSession()` — creates WorkItemSessionLink
1915
+ - `addAssociation()` — adds resource association (AgentDispatchRun, User, AgentSession)
1916
+ - `removeAssociation()` — removes resource association
1917
+ - `listAssociations()` — returns all associations
1918
+ - `getWorkspaceRuns()` — returns active and historical runs for this workspace
1919
+
1920
+ **Codespace Operations:**
1921
+ - `launchCodespace()` — generates Pod + Service specs for code-server IDE
1922
+ - `stopCodespace()` — generates delete manifests for codespace pod/service
1923
+ - `getCodespaceStatus()` — reports codespace running state and URL
1924
+
1925
+ **PVC Generation:**
1926
+ ```yaml
1927
+ apiVersion: v1
1928
+ kind: PersistentVolumeClaim
1929
+ metadata:
1930
+ name: krate-ws-{workspaceName}
1931
+ labels:
1932
+ krate.a5c.ai/workspace: {workspaceName}
1933
+ krate.a5c.ai/org: {organizationRef}
1934
+ spec:
1935
+ storageClassName: standard
1936
+ accessModes: [ReadWriteOnce]
1937
+ resources:
1938
+ requests:
1939
+ storage: 10Gi
1940
+ ```
1941
+
1942
+ **Relationships:**
1943
+ - Belongs to: Organization, Repository
1944
+ - Used by: AgentDispatchRun, RunnerPool pods
1945
+ - Contains: git worktree with checked-out code
1946
+ - Linked via: WorkItemWorkspaceLink (to Issues/PRs)
1947
+
1948
+ ---
1949
+
1950
+ #### KrateWorkspacePolicy
1951
+
1952
+ (Documented above in Agent Domain - section 1.5)
1953
+
1954
+ ---
1955
+
1956
+ #### KrateWorkspaceRuntime
1957
+
1958
+ | Field | Value |
1959
+ |-------|-------|
1960
+ | Storage | postgres |
1961
+ | Context | agents |
1962
+ | Plural | krateworkspaceruntimes |
1963
+ | Namespace | org-scoped |
1964
+
1965
+ **Purpose:** Workspace runtime surface state with current working directory, environment
1966
+ variables, process status, and preview URL.
1967
+
1968
+ **Required Spec Fields:**
1969
+ - `organizationRef` — owning organization
1970
+ - `workspaceRef` — reference to KrateWorkspace
1971
+ - `status` — current runtime status
1972
+
1973
+ **Status Fields:**
1974
+ - `phase` — Provisioning | Active | Stopped
1975
+ - `cwd` — current working directory
1976
+ - `env` — environment variables map
1977
+ - `processStatus` — running process info
1978
+ - `previewUrl` — if workspace exposes a preview
1979
+ - `createdAt` — creation timestamp
1980
+
1981
+ **Creation:**
1982
+ - Created by `provisionWorkspace()` alongside KrateWorkspace
1983
+ - Updated as workspace state changes
1984
+
1985
+ **Relationships:**
1986
+ - Belongs to: KrateWorkspace
1987
+ - Reflects: runtime state of the workspace pod
1988
+
1989
+ ---
1990
+
1991
+ ### 1.7 Memory Domain (7 kinds)
1992
+
1993
+ Resources for the "Company Brain" — organization-wide agent memory with graph/grep
1994
+ search, time-travel, imports, ontology validation, and snapshot pinning.
1995
+
1996
+ ---
1997
+
1998
+ #### AgentMemoryRepository
1999
+
2000
+ | Field | Value |
2001
+ |-------|-------|
2002
+ | Storage | etcd |
2003
+ | Context | agents |
2004
+ | Plural | agentmemoryrepositories |
2005
+ | Namespace | org-scoped |
2006
+
2007
+ **Purpose:** Organization-level Git repository pointer for shared agent memory with
2008
+ layout profile and index policy. The memory repository contains structured knowledge
2009
+ that agents can query.
2010
+
2011
+ **Required Spec Fields:**
2012
+ - `organizationRef` — owning organization
2013
+ - `repositoryRef` — reference to a Repository resource (the git repo storing memory)
2014
+ - `defaultBranch` — branch to read from (e.g., 'main')
2015
+ - `layoutProfile` — how memory is organized in the repo (e.g., 'flat', 'hierarchical')
2016
+
2017
+ **Optional Spec Fields:**
2018
+ - `indexPolicy` — how and when to rebuild indexes
2019
+ - `retentionPolicy` — how long to keep old memory
2020
+
2021
+ **Gitea Naming Convention:**
2022
+ - `orgMemoryRepositoryName(org)` → `_${org}_` (e.g., `_default_`)
2023
+ - Used as the Gitea repo name for memory storage
2024
+
2025
+ **Relationships:**
2026
+ - Belongs to: Organization
2027
+ - References: Repository (the backing git repo)
2028
+ - Has: AgentMemorySource (read policies), AgentMemoryOntology (schema)
2029
+ - Produces: AgentMemorySnapshot (at dispatch time)
2030
+
2031
+ ---
2032
+
2033
+ #### AgentMemorySource
2034
+
2035
+ | Field | Value |
2036
+ |-------|-------|
2037
+ | Storage | etcd |
2038
+ | Context | agents |
2039
+ | Plural | agentmemorysources |
2040
+ | Namespace | org-scoped |
2041
+
2042
+ **Purpose:** Read policy for memory paths and kinds per repository, team, stack, or
2043
+ trigger. Controls which parts of memory are available to which consumers.
2044
+
2045
+ **Required Spec Fields:**
2046
+ - `organizationRef` — owning organization
2047
+ - `repositoryRef` — which memory repository this applies to
2048
+ - `appliesTo` — scope (`{ kind, name }` — e.g., `{ kind: 'AgentStack', name: 'my-stack' }`)
2049
+ - `include` — paths/patterns to include
2050
+
2051
+ **Optional Spec Fields:**
2052
+ - `exclude` — paths/patterns to exclude
2053
+ - `kinds[]` — node kinds to include in graph queries
2054
+
2055
+ **Relationships:**
2056
+ - Belongs to: AgentMemoryRepository
2057
+ - Controls: what memory content is visible to specific stacks/teams
2058
+
2059
+ ---
2060
+
2061
+ #### AgentMemoryOntology
2062
+
2063
+ | Field | Value |
2064
+ |-------|-------|
2065
+ | Storage | etcd |
2066
+ | Context | agents |
2067
+ | Plural | agentmemoryontologies |
2068
+ | Namespace | org-scoped |
2069
+
2070
+ **Purpose:** Ontology policy pointer with required fields, edge kinds, and controlled
2071
+ vocabulary. Defines the schema that memory records must conform to.
2072
+
2073
+ **Required Spec Fields:**
2074
+ - `organizationRef` — owning organization
2075
+ - `memoryRepository` — reference to AgentMemoryRepository
2076
+ - `ontologyPath` — path within the memory repo where ontology is defined
2077
+
2078
+ **Ontology Structure:**
2079
+ - `requiredFields` — map of nodeKind → required field names
2080
+ - `allowedEdgeKinds` — array of valid edge relationship types
2081
+ - `controlledVocabulary` — terms that must be used consistently
2082
+
2083
+ **Validation (`validateOntology()`):**
2084
+ - Checks all records have required fields for their nodeKind
2085
+ - Checks all edge kinds are in allowedEdgeKinds list
2086
+
2087
+ **Relationships:**
2088
+ - Belongs to: AgentMemoryRepository
2089
+ - Enforced on: AgentMemoryUpdate submissions
2090
+ - Checked during: import processing
2091
+
2092
+ ---
2093
+
2094
+ #### AgentMemoryAssociation
2095
+
2096
+ | Field | Value |
2097
+ |-------|-------|
2098
+ | Storage | etcd |
2099
+ | Context | agents |
2100
+ | Plural | agentmemoryassociations |
2101
+ | Namespace | org-scoped |
2102
+
2103
+ **Purpose:** Bridge record linking memory content to Krate resources by relationship type.
2104
+ Enables bidirectional navigation between memory and resources.
2105
+
2106
+ **Required Spec Fields:**
2107
+ - `organizationRef` — owning organization
2108
+ - `memoryRef` — reference to a memory record (path or ID)
2109
+ - `targetRef` — reference to a Krate resource (`{ kind, name }`)
2110
+ - `relationship` — type of relationship (e.g., 'documents', 'implements', 'relates-to')
2111
+
2112
+ **Relationships:**
2113
+ - Links: memory records ↔ Krate resources
2114
+ - Enables: context-aware memory retrieval during dispatch
2115
+
2116
+ ---
2117
+
2118
+ #### AgentMemorySnapshot
2119
+
2120
+ | Field | Value |
2121
+ |-------|-------|
2122
+ | Storage | postgres |
2123
+ | Context | agents |
2124
+ | Plural | agentmemorysnapshots |
2125
+ | Namespace | org-scoped |
2126
+
2127
+ **Purpose:** Immutable dispatch-time memory pin with resolved commit, query manifest
2128
+ digest, and selected records digest. Captures the exact memory state used for a run.
2129
+
2130
+ **Required Spec Fields:**
2131
+ - `organizationRef` — owning organization
2132
+ - `memoryRepository` — which memory repository was snapshotted
2133
+ - `requestedRef` — the ref that was requested (branch, tag, commit)
2134
+ - `resolvedCommit` — the actual commit SHA used
2135
+
2136
+ **Optional Spec Fields:**
2137
+ - `queryManifestDigest` — hash of the query parameters used
2138
+ - `selectedRecordsDigest` — hash of selected graph records
2139
+ - `selectedDocumentsDigest` — hash of selected grep documents
2140
+ - `ontologyDigest` — hash of ontology at snapshot time
2141
+ - `recordCount` — number of selected records
2142
+ - `documentCount` — number of selected documents
2143
+
2144
+ **Time-Travel Modes (`resolveTimeTravel()`):**
2145
+ - `current` — use latest commit
2146
+ - `explicit-ref` — use specified ref directly
2147
+ - `ref-at-time` — find commit closest to but not after target time
2148
+ - `snapshot-tag` — use tagged snapshot commit
2149
+
2150
+ **Relationships:**
2151
+ - Belongs to: AgentDispatchRun (via memorySnapshotRef)
2152
+ - References: AgentMemoryRepository
2153
+ - Pinned at: specific git commit for reproducibility
2154
+
2155
+ ---
2156
+
2157
+ #### AgentMemoryQuery
2158
+
2159
+ | Field | Value |
2160
+ |-------|-------|
2161
+ | Storage | postgres |
2162
+ | Context | agents |
2163
+ | Plural | agentmemoryqueries |
2164
+ | Namespace | org-scoped |
2165
+
2166
+ **Purpose:** Graph and grep retrieval record with query parameters, result digests,
2167
+ and ranking metadata. Logs what queries were executed and their results.
2168
+
2169
+ **Required Spec Fields:**
2170
+ - `organizationRef` — owning organization
2171
+ - `snapshotRef` — reference to AgentMemorySnapshot used
2172
+ - `requester` — who/what executed the query
2173
+ - `query` — query parameters object
2174
+
2175
+ **Query Modes:**
2176
+ - `graph-only` — search graph records only
2177
+ - `grep-only` — search document content only
2178
+ - `graph-and-grep` — both (default)
2179
+
2180
+ **Graph Search:**
2181
+ - Filters by `kinds[]` (node kinds)
2182
+ - Traverses edges up to `edgeDepth` levels
2183
+ - Text matching against record content
2184
+ - Returns: `{ matches: [{ record, score, edges }], totalMatches }`
2185
+
2186
+ **Grep Search:**
2187
+ - Filters by `paths[]` patterns
2188
+ - Pattern matching against document content
2189
+ - Limited by `maxMatches` (default: 25)
2190
+ - Returns: `{ excerpts: [...], totalMatches }`
2191
+
2192
+ **Relationships:**
2193
+ - Belongs to: AgentMemorySnapshot
2194
+ - Records: query execution history for auditing
2195
+
2196
+ ---
2197
+
2198
+ #### AgentMemoryUpdate
2199
+
2200
+ | Field | Value |
2201
+ |-------|-------|
2202
+ | Storage | postgres |
2203
+ | Context | agents |
2204
+ | Plural | agentmemoryupdates |
2205
+ | Namespace | org-scoped |
2206
+
2207
+ **Purpose:** Reviewable proposed memory mutation with branch, changes, and validation
2208
+ status. Represents an agent's request to update the memory repository.
2209
+
2210
+ **Required Spec Fields:**
2211
+ - `organizationRef` — owning organization
2212
+ - `memoryRepository` — target memory repository
2213
+ - `sourceRun` — reference to the run proposing changes
2214
+ - `changes` — array of proposed changes
2215
+
2216
+ **Lifecycle Phases:**
2217
+ - `Pending` — update proposed, awaiting review
2218
+ - `Validated` — ontology checks passed
2219
+ - `Approved` — human approved the changes
2220
+ - `Committed` — changes committed to memory repo
2221
+ - `Rejected` — changes rejected
2222
+
2223
+ **Relationships:**
2224
+ - References: AgentMemoryRepository, AgentDispatchRun (source)
2225
+ - Validated against: AgentMemoryOntology
2226
+ - On commit: creates git commit in memory repository
2227
+
2228
+ ---
2229
+
2230
+ #### AgentRunMemoryImport
2231
+
2232
+ | Field | Value |
2233
+ |-------|-------|
2234
+ | Storage | postgres |
2235
+ | Context | agents |
2236
+ | Plural | agentrunmemoryimports |
2237
+ | Namespace | org-scoped |
2238
+
2239
+ **Purpose:** Import curated babysitter run metadata into the organization's company brain
2240
+ with redaction and review workflow.
2241
+
2242
+ **Required Spec Fields:**
2243
+ - `organizationRef` — owning organization
2244
+ - `memoryRepository` — target memory repository
2245
+ - `source` — source of the import (run reference, external URL, etc.)
2246
+ - `include` — what to include from the source
2247
+
2248
+ **Optional Spec Fields:**
2249
+ - `validationPolicy` — validation rules to apply (default: 'none')
2250
+
2251
+ **Import Pipeline Phases:**
2252
+ 1. `Pending` — import created
2253
+ 2. `Collecting` — gathering content from source
2254
+ 3. `Redacting` — scanning for secrets/credentials
2255
+ 4. `Normalizing` — converting to memory format
2256
+ 5. `Validating` — checking against ontology
2257
+ 6. `AwaitingReview` — ready for human review
2258
+
2259
+ **Redaction Scanning (`scanForRedaction()`):**
2260
+ Detects and replaces:
2261
+ - Secret keys (API_KEY=, PASSWORD=, etc.)
2262
+ - Provider tokens (sk-*, ghp_*, glpat-*, xoxb-*, etc.)
2263
+ - Bearer tokens
2264
+ - Private keys (PEM format)
2265
+ - Base64 credentials (40+ chars)
2266
+
2267
+ **Relationships:**
2268
+ - References: AgentMemoryRepository
2269
+ - Source: babysitter runs, external data
2270
+ - Validated by: AgentMemoryOntology
2271
+ - Produces: memory records on approval
2272
+
2273
+ ---
2274
+
2275
+ ### 1.8 External Backend Domain (10 kinds)
2276
+
2277
+ Resources managing bidirectional synchronization with external providers (GitHub, GitLab,
2278
+ etc.) including provider registration, binding, sync policy, webhook delivery,
2279
+ event normalization, state tracking, write intents, conflict resolution, object linking,
2280
+ and capability manifests.
2281
+
2282
+ ---
2283
+
2284
+ #### ExternalBackendProvider
2285
+
2286
+ | Field | Value |
2287
+ |-------|-------|
2288
+ | Storage | etcd |
2289
+ | Context | external-backends |
2290
+ | Plural | externalbackendproviders |
2291
+ | Namespace | org-scoped |
2292
+
2293
+ **Purpose:** External backend provider registration with type, endpoint, auth
2294
+ configuration, and capability discovery settings.
2295
+
2296
+ **Required Spec Fields:**
2297
+ - `organizationRef` — owning organization
2298
+ - `providerType` — provider identifier (e.g., 'github', 'gitlab', 'gitea')
2299
+ - `endpoint` — provider API endpoint URL
2300
+
2301
+ **Optional Spec Fields:**
2302
+ - `displayName` — human-readable provider name
2303
+ - `config` — provider-specific configuration
2304
+ - `authConfig` — authentication settings (app ID, installation ID, etc.)
2305
+
2306
+ **Lifecycle Phases:**
2307
+ - `Pending` — provider created, not yet authenticated
2308
+ - `Authenticating` — auth flow in progress
2309
+ - `Discovering` — capability discovery running
2310
+ - `Ready` — provider fully operational
2311
+ - `Degraded` — partial functionality (some APIs failing)
2312
+ - `Failed` — provider unreachable or auth invalid
2313
+
2314
+ **Creation (via `createExternalBackendProvider()`):**
2315
+ - Validates: name required, providerType required
2316
+ - Initial status.phase: `Pending`
2317
+ - Labels and annotations initialized empty
2318
+
2319
+ **Provider Registry (`createDefaultProviderRegistry()`):**
2320
+ - Auto-registers GitHub adapter
2321
+ - GitHub descriptor exposes: gitForge, issueTracking, cicd interfaces
2322
+ - Factory methods: `createForge()`, `createIssueTracker()`, `createCicd()`
2323
+
2324
+ **Relationships:**
2325
+ - Belongs to: Organization
2326
+ - Has: ExternalBackendBinding (0:N)
2327
+ - Describes: ExternalProviderCapabilityManifest (1:1)
2328
+ - Authenticated via: GitHub App JWT + installation token exchange
2329
+
2330
+ ---
2331
+
2332
+ #### ExternalBackendBinding
2333
+
2334
+ | Field | Value |
2335
+ |-------|-------|
2336
+ | Storage | etcd |
2337
+ | Context | external-backends |
2338
+ | Plural | externalbackendbindings |
2339
+ | Namespace | org-scoped |
2340
+
2341
+ **Purpose:** Binding of an external backend provider to an organization with credential
2342
+ reference and sync scope. Activates synchronization for specific resources.
2343
+
2344
+ **Required Spec Fields:**
2345
+ - `organizationRef` — owning organization
2346
+ - `providerRef` — reference to ExternalBackendProvider
2347
+ - `credentialRef` — reference to K8s Secret with provider credentials
2348
+
2349
+ **Optional Spec Fields:**
2350
+ - `scope` — what to sync: `{ repositories[], issues, pullRequests, pipelines }`
2351
+ - `webhookSecret` — shared secret for webhook HMAC verification
2352
+ - `syncEnabled` — boolean toggle
2353
+
2354
+ **Lifecycle Phases:**
2355
+ - `Pending` — binding created, not yet validated
2356
+ - `ValidatingTarget` — checking provider connectivity
2357
+ - `RegisteringWebhook` — setting up webhook on provider
2358
+ - `Backfilling` — importing existing data from provider
2359
+ - `Ready` — bidirectional sync active
2360
+ - `Degraded` — sync partially working
2361
+ - `Failed` — sync completely broken
2362
+
2363
+ **Relationships:**
2364
+ - Belongs to: Organization
2365
+ - References: ExternalBackendProvider
2366
+ - Controls: ExternalBackendSyncPolicy (sync behavior)
2367
+ - Produces: ExternalWebhookDelivery, ExternalSyncEvent, ExternalObjectLink
2368
+ - Manages: webhook registration on external provider
2369
+
2370
+ ---
2371
+
2372
+ #### ExternalBackendSyncPolicy
2373
+
2374
+ | Field | Value |
2375
+ |-------|-------|
2376
+ | Storage | etcd |
2377
+ | Context | external-backends |
2378
+ | Plural | externalbackendsyncpolicies |
2379
+ | Namespace | org-scoped |
2380
+
2381
+ **Purpose:** Sync interval, conflict resolution mode, field mapping overrides, and retry
2382
+ policy for an external backend provider.
2383
+
2384
+ **Required Spec Fields:**
2385
+ - `organizationRef` — owning organization
2386
+ - `providerRef` — reference to ExternalBackendProvider
2387
+ - `syncInterval` — how often to poll for changes (e.g., '5m', '1h')
2388
+
2389
+ **Optional Spec Fields:**
2390
+ - `conflictResolution` — default strategy: `prefer-external`, `prefer-krate`, `manual`
2391
+ - `fieldMappingOverrides` — custom field mappings
2392
+ - `retryPolicy` — `{ maxRetries, backoffMs }`
2393
+ - `webhookFirst` — boolean (prefer webhooks over polling)
2394
+ - `backfillInterval` — how often to do full backfill
2395
+
2396
+ **Ownership Modes (applied by sync-controller):**
2397
+ - `bidirectional` — both krate and external may write
2398
+ - `external-owned` — external is authoritative; krate is read-only
2399
+ - `krate-owned` — krate is authoritative; external is read-only
2400
+
2401
+ **Relationships:**
2402
+ - Belongs to: ExternalBackendProvider/Binding
2403
+ - Controls: sync-controller behavior
2404
+ - Determines: conflict resolution strategy
2405
+
2406
+ ---
2407
+
2408
+ #### ExternalProviderCapabilityManifest
2409
+
2410
+ | Field | Value |
2411
+ |-------|-------|
2412
+ | Storage | etcd |
2413
+ | Context | external-backends |
2414
+ | Plural | externalprovidercapabilitymanifests |
2415
+ | Namespace | org-scoped |
2416
+
2417
+ **Purpose:** Discovered capability surface of an external backend provider including
2418
+ supported resource kinds and API features.
2419
+
2420
+ **Required Spec Fields:**
2421
+ - `organizationRef` — owning organization
2422
+ - `providerRef` — reference to ExternalBackendProvider
2423
+ - `capabilities` — capability description
2424
+
2425
+ **Capability Manifest Structure:**
2426
+ - `providerType` — provider type string
2427
+ - `interfaces[]` — implemented interfaces: `gitForge`, `issueTracking`, `cicd`
2428
+
2429
+ **Validation (`validateCapabilityManifest()`):**
2430
+ - providerType: required non-empty string
2431
+ - interfaces: non-empty array of known interface names
2432
+ - Unknown interfaces rejected
2433
+
2434
+ **Provider Adapter Validation (`validateProviderAdapter()`):**
2435
+ Required contract:
2436
+ - `descriptor()` → `{ providerType, displayName, hosting, authModes, apiCapabilities }`
2437
+ - `health()` → `{ status: 'healthy'|'degraded'|'unavailable', message }`
2438
+ - At least one interface: issueTracking, cicd, or gitForge
2439
+ - `normalizeWebhook(payload)` → NormalizedEvent[]
2440
+ - `verifyWebhook(request)` → `{ valid, reason }`
2441
+
2442
+ **Relationships:**
2443
+ - Belongs to: ExternalBackendProvider
2444
+ - Describes: what the provider can do
2445
+ - Used by: platform for routing sync operations to correct interface
2446
+
2447
+ ---
2448
+
2449
+ #### ExternalWebhookDelivery
2450
+
2451
+ | Field | Value |
2452
+ |-------|-------|
2453
+ | Storage | postgres |
2454
+ | Context | external-backends |
2455
+ | Plural | externalwebhookdeliveries |
2456
+ | Namespace | org-scoped |
2457
+
2458
+ **Purpose:** Inbound webhook delivery from an external backend provider with event type,
2459
+ payload, and processing state.
2460
+
2461
+ **Required Spec Fields:**
2462
+ - `organizationRef` — owning organization
2463
+ - `providerRef` — which provider sent this
2464
+ - `eventType` — webhook event type (e.g., 'push', 'pull_request', 'issues')
2465
+ - `payload` — raw webhook payload
2466
+
2467
+ **Lifecycle Phases:**
2468
+ - `Received` — webhook received, signature verified
2469
+ - `Queued` — in processing queue
2470
+ - `Normalizing` — converting to canonical event format
2471
+ - `Processing` — being processed by sync controller
2472
+ - `Succeeded` — successfully processed
2473
+ - `DeadLettered` — processing failed after retries
2474
+
2475
+ **Webhook Processing (via webhook-controller):**
2476
+ 1. Verify HMAC-SHA256 signature (timing-safe comparison)
2477
+ 2. Check deduplication by deliveryId
2478
+ 3. Create delivery record with timestamp
2479
+ 4. Emit to subscriber queue
2480
+ 5. Process: normalize event → upsert resources → update watermark
2481
+
2482
+ **HMAC Verification:**
2483
+ - Signature format: `sha256={hex digest}`
2484
+ - Uses `crypto.createHmac('sha256', secret)`
2485
+ - Timing-safe comparison via `crypto.timingSafeEqual()`
2486
+ - Rejects: missing signature, invalid format, mismatched HMAC
2487
+
2488
+ **Relationships:**
2489
+ - From: ExternalBackendProvider (via webhook)
2490
+ - Produces: ExternalSyncEvent (normalized)
2491
+ - Tracked by: ExternalSyncState (watermark)
2492
+
2493
+ ---
2494
+
2495
+ #### ExternalSyncEvent
2496
+
2497
+ | Field | Value |
2498
+ |-------|-------|
2499
+ | Storage | postgres |
2500
+ | Context | external-backends |
2501
+ | Plural | externalsyncevents |
2502
+ | Namespace | org-scoped |
2503
+
2504
+ **Purpose:** Discrete sync event record from an external backend for a specific resource
2505
+ kind with deduplication and ordering metadata.
2506
+
2507
+ **Required Spec Fields:**
2508
+ - `organizationRef` — owning organization
2509
+ - `providerRef` — source provider
2510
+ - `eventKind` — event category
2511
+ - `resourceRef` — affected resource reference
2512
+
2513
+ **Event Normalization (via sync-controller `normalizeEvent()`):**
2514
+ Input:
2515
+ ```
2516
+ { eventType, action, nativeId, providerRef, resourceKind, data, receivedAt }
2517
+ ```
2518
+ Output (canonical format):
2519
+ ```
2520
+ { eventType, action, nativeId, providerRef, resourceKind, data, receivedAt, canonicalAt }
2521
+ ```
2522
+
2523
+ **Relationships:**
2524
+ - Produced by: ExternalWebhookDelivery processing
2525
+ - Updates: ExternalSyncState (watermark)
2526
+ - May produce: ExternalSyncConflict (on field divergence)
2527
+
2528
+ ---
2529
+
2530
+ #### ExternalSyncState
2531
+
2532
+ | Field | Value |
2533
+ |-------|-------|
2534
+ | Storage | postgres |
2535
+ | Context | external-backends |
2536
+ | Plural | externalsyncstates |
2537
+ | Namespace | org-scoped |
2538
+
2539
+ **Purpose:** Current sync phase, last successful sync timestamp, and error details for
2540
+ an external resource binding. Implements high-watermark tracking.
2541
+
2542
+ **Required Spec Fields:**
2543
+ - `organizationRef` — owning organization
2544
+ - `providerRef` — source provider
2545
+ - `resourceRef` — the resource being tracked
2546
+ - `phase` — current sync phase
2547
+
2548
+ **Watermark Tracking (via sync-controller):**
2549
+ - `updateWatermark(bindingRef, timestamp)` — advances if newer than current
2550
+ - `getWatermark(bindingRef)` — returns current watermark or null
2551
+ - Persisted as CRD-shaped resource with bindingRef and timestamp
2552
+
2553
+ **Relationships:**
2554
+ - Tracks: sync progress for each binding/resource pair
2555
+ - Updated by: sync-controller after successful event processing
2556
+ - Used for: resume-from-last-known-good on reconnection
2557
+
2558
+ ---
2559
+
2560
+ #### ExternalWriteIntent
2561
+
2562
+ | Field | Value |
2563
+ |-------|-------|
2564
+ | Storage | postgres |
2565
+ | Context | external-backends |
2566
+ | Plural | externalwriteintents |
2567
+ | Namespace | org-scoped |
2568
+
2569
+ **Purpose:** Queued write-back intent to an external backend with operation, payload
2570
+ snapshot, and approval state. Manages the lifecycle of outbound mutations.
2571
+
2572
+ **Required Spec Fields:**
2573
+ - `organizationRef` — owning organization
2574
+ - `providerRef` — target provider
2575
+ - `resourceRef` — resource being written
2576
+ - `operation` — operation type (e.g., 'create', 'update', 'delete')
2577
+
2578
+ **Optional Spec Fields:**
2579
+ - `interfaceKey` — which interface to use (gitForge, issueTracking, cicd)
2580
+ - `payload` — operation payload
2581
+ - `requiresApproval` — boolean
2582
+ - `maxRetries` — max retry attempts (default: 3)
2583
+ - `idempotencyKey` — deterministic key for dedup
2584
+
2585
+ **Lifecycle Phases:**
2586
+ - `PendingApproval` — write requires human approval
2587
+ - `ReadyToSend` — approved (or no approval needed), ready for execution
2588
+ - `Sending` — write in progress
2589
+ - `Retrying` — write failed, retrying
2590
+ - `Succeeded` — write completed successfully
2591
+ - `Failed` — write failed after all retries exhausted
2592
+ - `Rejected` — write approval denied
2593
+
2594
+ **Idempotency Key Generation (`getIdempotencyKey()`):**
2595
+ - Deterministic hash of: interfaceKey + operation + resourceRef + payload
2596
+ - Format: `idem-{interfaceKey}-{operation}-{hash}`
2597
+ - Prevents duplicate writes for identical operations
2598
+
2599
+ **Operations:**
2600
+ - `createWriteIntent()` — creates with validation and idempotency key
2601
+ - `approveWriteIntent()` — PendingApproval → ReadyToSend
2602
+ - `rejectWriteIntent()` — PendingApproval → Rejected
2603
+ - `executeWriteIntent()` — ReadyToSend → Sending → Succeeded/Retrying/Failed
2604
+
2605
+ **Execution (`executeWriteIntent()`):**
2606
+ 1. Verify phase is ReadyToSend
2607
+ 2. Transition to Sending
2608
+ 3. Call executor function
2609
+ 4. On success: → Succeeded (with externalResult)
2610
+ 5. On failure: increment retry, → Retrying (if retries remain)
2611
+ 6. On exhaustion: → Failed (with lastError)
2612
+
2613
+ **Relationships:**
2614
+ - Targets: ExternalBackendProvider (via providerRef)
2615
+ - May require: AgentApproval (if requiresApproval)
2616
+ - May produce: ExternalSyncConflict (on conflict during write)
2617
+
2618
+ ---
2619
+
2620
+ #### ExternalSyncConflict
2621
+
2622
+ | Field | Value |
2623
+ |-------|-------|
2624
+ | Storage | postgres |
2625
+ | Context | external-backends |
2626
+ | Plural | externalsyncconflicts |
2627
+ | Namespace | org-scoped |
2628
+
2629
+ **Purpose:** Detected conflict between local Krate state and external provider state
2630
+ with conflict kind, field-level diff, and resolution outcome.
2631
+
2632
+ **Required Spec Fields:**
2633
+ - `organizationRef` — owning organization
2634
+ - `providerRef` — provider where conflict was detected
2635
+ - `resourceRef` — affected resource
2636
+ - `conflictKind` — type of conflict
2637
+
2638
+ **Optional Spec Fields:**
2639
+ - `fieldPath` — specific field in conflict
2640
+ - `localValue` — Krate's value for the field
2641
+ - `externalValue` — external provider's value
2642
+ - `detectedAt` — when conflict was found
2643
+
2644
+ **Lifecycle Phases:**
2645
+ - `Open` — conflict detected, unresolved
2646
+ - `Resolving` — resolution in progress
2647
+ - `Resolved` — conflict resolved (with chosen value)
2648
+ - `Ignored` — conflict intentionally ignored
2649
+ - `Superseded` — new sync event made this conflict irrelevant
2650
+
2651
+ **Resolution Strategies:**
2652
+ - `prefer-external` — use external provider's value
2653
+ - `prefer-krate` — use Krate's value
2654
+ - `manual` — use explicitly provided resolvedValue
2655
+ - `ignore` — mark as Ignored, no value chosen
2656
+
2657
+ **Operations:**
2658
+ - `detectConflict()` — creates conflict if localValue !== externalValue
2659
+ - `resolveConflict()` — applies strategy, transitions to Resolved/Ignored
2660
+ - `supersededCheck()` — marks all Open conflicts for a resource/field as Superseded
2661
+ - `getOpenConflicts()` — lists all Open (unresolved) conflicts
2662
+
2663
+ **Relationships:**
2664
+ - References: ExternalBackendProvider, affected resource
2665
+ - Resolved by: human operator or automated policy
2666
+ - May block: ExternalWriteIntent (if unresolved)
2667
+
2668
+ ---
2669
+
2670
+ #### ExternalObjectLink
2671
+
2672
+ | Field | Value |
2673
+ |-------|-------|
2674
+ | Storage | postgres |
2675
+ | Context | external-backends |
2676
+ | Plural | externalobjectlinks |
2677
+ | Namespace | org-scoped |
2678
+
2679
+ **Purpose:** Stable mapping between a Krate local resource and its external backend
2680
+ counterpart. The identity envelope that tracks external IDs, URLs, and ETags.
2681
+
2682
+ **Required Spec Fields:**
2683
+ - `organizationRef` — owning organization
2684
+ - `providerRef` — external provider
2685
+ - `externalId` — native ID on the external system (e.g., GitHub node ID)
2686
+ - `localRef` — reference to local Krate resource
2687
+
2688
+ **Optional Spec Fields (in status.external on synced resources):**
2689
+ - `nativeId` — external system's identifier
2690
+ - `url` — external URL (e.g., GitHub PR URL)
2691
+ - `etag` — HTTP ETag for change detection
2692
+ - `lastSyncedAt` — last successful sync timestamp
2693
+ - `firstSyncedAt` — when first synced
2694
+
2695
+ **Upsert Behavior (via sync-controller `upsertResource()`):**
2696
+ - Creates resource with external envelope in status
2697
+ - Preserves `firstSyncedAt` from existing record
2698
+ - Updates `lastSyncedAt` to current time
2699
+ - Sets phase to `Synced`
2700
+
2701
+ **Relationships:**
2702
+ - Links: local Krate resource ↔ external resource
2703
+ - Used by: sync controller for bidirectional mapping
2704
+ - Enables: URL resolution, change detection (ETag), dedup
2705
+
2706
+ ---
2707
+
2708
+ ### 1.9 Control Plane Domain (3 kinds)
2709
+
2710
+ ---
2711
+
2712
+ #### PullRequest
2713
+
2714
+ | Field | Value |
2715
+ |-------|-------|
2716
+ | Storage | postgres |
2717
+ | Context | control-plane |
2718
+ | Plural | pullrequests |
2719
+ | Namespace | org-scoped |
2720
+
2721
+ **Purpose:** Review unit with source/target refs, title, checks, and merge lifecycle.
2722
+
2723
+ **Required Spec Fields:**
2724
+ - `organizationRef` — owning organization
2725
+ - `repository` — target repository
2726
+ - `title` — PR title
2727
+
2728
+ **Gitea Integration:**
2729
+ - `createGiteaBackend().createPullRequest({ owner, repo, title, head, base, body })`
2730
+
2731
+ **GitHub Integration:**
2732
+ - `GitHubGitForge.createPullRequest({ repo, title, head, base, body })`
2733
+ - `GitHubGitForge.getPullRequest({ repo, pullNumber })`
2734
+ - `GitHubGitForge.mergePullRequest({ repo, pullNumber, mergeMethod })`
2735
+ - Normalized: `{ number, title, state, head, base, body, merged, htmlUrl }`
2736
+
2737
+ **Relationships:**
2738
+ - Belongs to: Organization, Repository
2739
+ - Has: Review (0:N)
2740
+ - Linked via: WorkItemSessionLink (to agent sessions)
2741
+ - Synced from: GitHub PRs via ExternalBackendBinding
2742
+
2743
+ ---
2744
+
2745
+ #### Issue
2746
+
2747
+ | Field | Value |
2748
+ |-------|-------|
2749
+ | Storage | postgres |
2750
+ | Context | control-plane |
2751
+ | Plural | issues |
2752
+ | Namespace | org-scoped |
2753
+
2754
+ **Purpose:** Project-scoped work item with labels, comments, backend sync metadata,
2755
+ and zero-or-more repository associations.
2756
+
2757
+ **Required Spec Fields:**
2758
+ - `organizationRef` — owning organization
2759
+ - `title` — issue title
2760
+
2761
+ **Optional Spec Fields:**
2762
+ - `repositoryRefs[]` — associated repositories
2763
+ - `projectRefs[]` — associated projects
2764
+ - `workflowState` — kanban column ID
2765
+ - `labels[]` — issue labels
2766
+ - `assignees[]` — assigned users
2767
+
2768
+ **Gitea Integration:**
2769
+ - `createGiteaBackend().createIssue({ owner, repo, title, body, labels, assignees })`
2770
+ - `giteaIssueSyncPlan()` — plans: ensureOrgMemoryRepository, syncIssue, writeIssueRepositoryMetadata
2771
+
2772
+ **GitHub Integration:**
2773
+ - `GitHubIssueTracking.listIssues({ repo, state })`
2774
+ - `GitHubIssueTracking.createIssue({ repo, title, body, labels })`
2775
+ - `GitHubIssueTracking.updateIssue({ repo, issueNumber, title, body, labels })`
2776
+ - `GitHubIssueTracking.closeIssue({ repo, issueNumber })`
2777
+ - Normalized: `{ id, number, title, state, body, labels, author, htmlUrl }`
2778
+ - `githubProjectIssueSyncPlan()` — plans: syncProjectItem, syncIssueMetadata, syncRepositoryLinks
2779
+
2780
+ **Project Relationship:**
2781
+ - Issues belong to KrateProjects via `projectRefs`
2782
+ - `workflowState` corresponds to project's `workflowColumns[].id`
2783
+ - Kanban drag-drop updates workflowState
2784
+ - Board derives columns from `project.spec.workflowColumns`
2785
+
2786
+ **Relationships:**
2787
+ - Belongs to: Organization
2788
+ - References: Repository (0:N via repositoryRefs), KrateProject (0:N via projectRefs)
2789
+ - Has: Comments (via GitHub/Gitea sync)
2790
+ - Linked via: WorkItemSessionLink, WorkItemWorkspaceLink
2791
+ - Synced from: GitHub Issues, Gitea Issues
2792
+
2793
+ ---
2794
+
2795
+ #### Review
2796
+
2797
+ | Field | Value |
2798
+ |-------|-------|
2799
+ | Storage | postgres |
2800
+ | Context | control-plane |
2801
+ | Plural | reviews |
2802
+ | Namespace | org-scoped |
2803
+
2804
+ **Purpose:** Approval, comment, or change-request record for a pull request.
2805
+
2806
+ **Required Spec Fields:**
2807
+ - `organizationRef` — owning organization
2808
+ - `pullRequest` — reference to the PullRequest
2809
+
2810
+ **Optional Spec Fields:**
2811
+ - `state` — `approved`, `changes_requested`, `commented`
2812
+ - `body` — review body text
2813
+ - `author` — reviewer username
2814
+
2815
+ **Relationships:**
2816
+ - Belongs to: PullRequest
2817
+ - Synced from: GitHub PR reviews
2818
+
2819
+ ---
2820
+
2821
+ ### 1.10 CI/CD Domain (3 kinds)
2822
+
2823
+ ---
2824
+
2825
+ #### Pipeline
2826
+
2827
+ | Field | Value |
2828
+ |-------|-------|
2829
+ | Storage | postgres |
2830
+ | Context | runners-ci |
2831
+ | Plural | pipelines |
2832
+ | Namespace | org-scoped |
2833
+
2834
+ **Purpose:** CI pipeline run state with trust tier, steps, current step, and resume point.
2835
+
2836
+ **Required Spec Fields:**
2837
+ - `organizationRef` — owning organization
2838
+ - `repository` — target repository
2839
+ - `ref` — git ref (branch, tag, commit)
2840
+
2841
+ **Optional Spec Fields:**
2842
+ - `actor` — who triggered the pipeline
2843
+ - `steps[]` — ordered step names (e.g., ['checkout', 'test', 'build'])
2844
+ - `trustTier` — `trusted` or `untrusted` (fork = untrusted)
2845
+ - `resumeFrom` — step to resume from (for reruns)
2846
+
2847
+ **Creation (via `RunnerScheduler.startPipeline()`):**
2848
+ - Creates Pipeline resource with phase: Running
2849
+ - Creates Job resource for each step
2850
+ - First job (or resumeFrom) starts as Running, others as Pending
2851
+ - Trust tier from fork status (fork = untrusted)
2852
+
2853
+ **Rerun:**
2854
+ - `RunnerScheduler.rerunFromStep()` — creates new pipeline with resumeFrom
2855
+
2856
+ **GitHub Integration:**
2857
+ - `GitHubCicd.listWorkflowRuns({ repo, workflowId })`
2858
+ - Normalized: `{ id, name, status, conclusion, headBranch, headSha, htmlUrl, createdAt }`
2859
+ - External pipeline events via webhook → ExternalWebhookDelivery → Pipeline projection
2860
+
2861
+ **Relationships:**
2862
+ - Belongs to: Organization, Repository
2863
+ - Has: Job (1:N, one per step)
2864
+ - Scheduled on: RunnerPool
2865
+ - Synced from: GitHub Actions workflow_run events
2866
+
2867
+ ---
2868
+
2869
+ #### Job
2870
+
2871
+ | Field | Value |
2872
+ |-------|-------|
2873
+ | Storage | postgres |
2874
+ | Context | runners-ci |
2875
+ | Plural | jobs |
2876
+ | Namespace | org-scoped |
2877
+
2878
+ **Purpose:** Executable CI step with service-account scope and isolation metadata.
2879
+
2880
+ **Required Spec Fields:**
2881
+ - `organizationRef` — owning organization
2882
+ - `pipeline` — reference to parent Pipeline
2883
+ - `step` — step name
2884
+
2885
+ **Optional Spec Fields:**
2886
+ - `serviceAccount` — computed service account name for RBAC isolation
2887
+
2888
+ **Service Account Generation (`serviceAccountForJob()`):**
2889
+ - Format: `krate-runner-{namespace}-{repository}-{pipeline}-{trustTier}`
2890
+ - Ensures each job runs with appropriate permissions
2891
+
2892
+ **Lifecycle Phases:**
2893
+ - `Pending` — waiting for runner assignment
2894
+ - `Running` — executing on a runner
2895
+ - `Succeeded` — completed successfully
2896
+ - `Failed` — execution failed
2897
+
2898
+ **GitHub Integration:**
2899
+ - `GitHubCicd.listJobs({ repo, runId })`
2900
+ - Normalized: `{ id, name, status, conclusion, startedAt, completedAt, htmlUrl }`
2901
+
2902
+ **Runner Assignment:**
2903
+ - `runnerController.scheduleJob(pool, job)` — assigns job to idle runner or creates new
2904
+ - Volume: workspace PVC mounted at /workspace
2905
+ - Environment: KRATE_ORG, KRATE_RUN_ID, KRATE_WORKSPACE_PATH
2906
+
2907
+ **Relationships:**
2908
+ - Belongs to: Pipeline
2909
+ - Scheduled on: RunnerPool runner
2910
+ - Has: service account for RBAC isolation
2911
+
2912
+ ---
2913
+
2914
+ #### WebhookDelivery (outbound)
2915
+
2916
+ | Field | Value |
2917
+ |-------|-------|
2918
+ | Storage | postgres |
2919
+ | Context | hooks-events |
2920
+ | Plural | webhookdeliveries |
2921
+ | Namespace | org-scoped |
2922
+
2923
+ **Purpose:** Durable outbound webhook delivery attempt with signature, phase, response,
2924
+ and replay metadata.
2925
+
2926
+ **Required Spec Fields:**
2927
+ - `organizationRef` — owning organization
2928
+ - `subscription` — reference to WebhookSubscription
2929
+ - `eventType` — event type being delivered
2930
+ - `signature` — HMAC signature of payload
2931
+
2932
+ **Lifecycle Phases:**
2933
+ - `Pending` — queued for delivery
2934
+ - `Delivering` — HTTP request in flight
2935
+ - `Delivered` — 2xx response received
2936
+ - `Failed` — non-2xx response or network error
2937
+ - `Retrying` — failed, will retry
2938
+
2939
+ **Relationships:**
2940
+ - Belongs to: WebhookSubscription
2941
+ - Contains: delivery attempt history
2942
+ - Triggered by: resource-change events via event bus
2943
+
2944
+ ---
2945
+
2946
+ ## PART 2: External Backend Relationships
2947
+
2948
+ ---
2949
+
2950
+ ### 2.1 Gitea Integration
2951
+
2952
+ Gitea serves as the default git hosting backend. The integration is implemented in
2953
+ `gitea-backend.js` and `gitea-service.js`.
2954
+
2955
+ #### Repository ↔ Gitea Repo
2956
+
2957
+ | Krate Operation | Gitea API Call |
2958
+ |-----------------|----------------|
2959
+ | Create Repository | `POST /api/v1/orgs/{owner}/repos` (org) or `POST /api/v1/user/repos` (personal) |
2960
+ | Private flag | Derived from visibility: private/internal → `private: true`, public → `private: false` |
2961
+ | Default branch | `default_branch` parameter |
2962
+ | Auto-init | `auto_init: false` (repo initialized externally) |
2963
+
2964
+ #### SSHKey ↔ Gitea Deploy Keys
2965
+
2966
+ | Krate Operation | Gitea API Call |
2967
+ |-----------------|----------------|
2968
+ | Add deploy key | `POST /api/v1/repos/{owner}/{repo}/keys` with `{ title, key, read_only }` |
2969
+ | Add user key | `POST /api/v1/user/keys` with `{ title, key, read_only }` |
2970
+ | Remove key | (not exposed in current backend — reconciler handles) |
2971
+
2972
+ #### RepositoryPermission ↔ Gitea Collaborators
2973
+
2974
+ | Krate Operation | Gitea API Call |
2975
+ |-----------------|----------------|
2976
+ | Add collaborator | `PUT /api/v1/repos/{owner}/{repo}/collaborators/{username}` with `{ permission }` |
2977
+ | Team repository | `PUT /api/v1/teams/{team}/repos/{owner}/{repo}` with `{ permission }` |
2978
+ | Permission levels | `read`, `write`, `admin` (direct mapping) |
2979
+
2980
+ #### Team ↔ Gitea Teams
2981
+
2982
+ | Krate Operation | Gitea API Call |
2983
+ |-----------------|----------------|
2984
+ | Create team | `POST /api/v1/orgs/{org}/teams` with `{ name, permission, units }` |
2985
+ | Add member | `PUT /api/v1/teams/{team}/members/{username}` |
2986
+ | Default units | `['repo.code', 'repo.pulls', 'repo.issues']` |
2987
+
2988
+ #### BranchProtection ↔ Gitea Branch Protection
2989
+
2990
+ | Krate Operation | Gitea API Call |
2991
+ |-----------------|----------------|
2992
+ | Protect branch | `POST /api/v1/repos/{owner}/{repo}/branch_protections` |
2993
+ | Parameters | `branch_name`, `enable_push: false`, `enable_push_whitelist: true`, `required_approvals`, status checks |
2994
+
2995
+ #### Issue/PR ↔ Gitea
2996
+
2997
+ | Krate Operation | Gitea API Call |
2998
+ |-----------------|----------------|
2999
+ | Create issue | `POST /api/v1/repos/{owner}/{repo}/issues` |
3000
+ | Create PR | `POST /api/v1/repos/{owner}/{repo}/pulls` |
3001
+ | Create webhook | `POST /api/v1/repos/{owner}/{repo}/hooks` (type: 'gitea') |
3002
+
3003
+ #### Git Tree/Blob API (gitea-service.js)
3004
+
3005
+ | Operation | Gitea API Call |
3006
+ |-----------|----------------|
3007
+ | listTree | `GET /api/v1/repos/{owner}/{repo}/contents/{path}?ref={ref}` |
3008
+ | getBlob | `GET /api/v1/repos/{owner}/{repo}/raw/{filepath}?ref={ref}` |
3009
+ | listBranches | `GET /api/v1/repos/{owner}/{repo}/branches` |
3010
+
3011
+ **Fallback Behavior:**
3012
+ - `createGiteaService()` returns `null` when `KRATE_GITEA_HTTP_URL` is not set
3013
+ - Callers fall back to mock data when service is null
3014
+ - 404 responses return null (graceful degradation)
3015
+
3016
+ #### Repository Integration Plan (`giteaRepositoryIntegrationPlan()`)
3017
+
3018
+ Complete integration requires these sequential operations:
3019
+ 1. `createOrganization` — ensure Gitea org exists
3020
+ 2. `createRepository` — create the repo
3021
+ 3. `ensureUserMappings` — map Krate users to Gitea users
3022
+ 4. `addDeployKey` — add GitOps deploy key (read/write)
3023
+ 5. `addUserSshKey` — add developer keys
3024
+ 6. `addCollaborator` — set permissions
3025
+ 7. `addTeamRepository` — grant team access (maintainers: admin)
3026
+ 8. `protectBranch` — protect main branch
3027
+ 9. `createWebhook` — register event webhook
3028
+
3029
+ #### Issue Sync Plan (`giteaIssueSyncPlan()`)
3030
+
3031
+ For issue synchronization with Gitea:
3032
+ 1. `ensureOrgMemoryRepository` — ensure `_${org}_` repo exists
3033
+ 2. `syncIssue` — create/update issue in Gitea
3034
+ 3. `writeIssueRepositoryMetadata` — write metadata labels linking issue to repositories
3035
+
3036
+ ---
3037
+
3038
+ ### 2.2 GitHub Integration (External Backend)
3039
+
3040
+ GitHub is implemented as the first ExternalBackendProvider with full adapter support.
3041
+
3042
+ #### Authentication Flow
3043
+
3044
+ 1. **JWT Creation (`createGitHubJwt()`):**
3045
+ - Encodes: `{ iat, exp, iss: appId }` with RS256 (production) or HS256 (test)
3046
+ - PEM key detection: looks for `-----BEGIN` prefix
3047
+ - Produces: signed JWT for GitHub App authentication
3048
+
3049
+ 2. **Installation Token Exchange (`exchangeInstallationToken()`):**
3050
+ - Endpoint: `POST /app/installations/{installationId}/access_tokens`
3051
+ - Authorization: `Bearer {appJwt}`
3052
+ - Returns: `{ token, expiresAt }`
3053
+ - Token used for all subsequent API calls
3054
+
3055
+ #### Git Forge Interface (GitHubGitForge)
3056
+
3057
+ | Method | GitHub API | Returns |
3058
+ |--------|-----------|---------|
3059
+ | `listRepositories()` | `GET /installation/repositories` | `NormalizedRepo[]` |
3060
+ | `getPullRequest({ repo, pullNumber })` | `GET /repos/{owner}/{repo}/pulls/{number}` | `NormalizedPR` |
3061
+ | `createPullRequest({ repo, title, head, base, body })` | `POST /repos/{owner}/{repo}/pulls` | `NormalizedPR` |
3062
+ | `mergePullRequest({ repo, pullNumber, mergeMethod })` | `PUT /repos/{owner}/{repo}/pulls/{number}/merge` | `{ merged, sha, message }` |
3063
+ | `listRefs({ repo })` | `GET /repos/.../branches` + `GET /repos/.../tags` | `{ branches, tags }` |
3064
+ | `syncDeployKeys({ repo, desiredKeys })` | GET+DELETE+POST `/repos/.../keys` | `{ added, removed }` |
3065
+ | `syncBranchProtection({ repo, branch, ... })` | `PUT /repos/.../branches/{branch}/protection` | protection object |
3066
+
3067
+ #### Issue Tracking Interface (GitHubIssueTracking)
3068
+
3069
+ | Method | GitHub API | Returns |
3070
+ |--------|-----------|---------|
3071
+ | `listIssues({ repo, state })` | `GET /repos/{owner}/{repo}/issues?state={state}` | `NormalizedIssue[]` |
3072
+ | `createIssue({ repo, title, body, labels })` | `POST /repos/{owner}/{repo}/issues` | `NormalizedIssue` |
3073
+ | `updateIssue({ repo, issueNumber, ... })` | `PATCH /repos/{owner}/{repo}/issues/{number}` | `NormalizedIssue` |
3074
+ | `closeIssue({ repo, issueNumber })` | `PATCH .../issues/{number}` (state: closed) | `NormalizedIssue` |
3075
+ | `listComments({ repo, issueNumber })` | `GET /repos/.../issues/{number}/comments` | `NormalizedComment[]` |
3076
+ | `createComment({ repo, issueNumber, body })` | `POST /repos/.../issues/{number}/comments` | `NormalizedComment` |
3077
+
3078
+ #### CI/CD Interface (GitHubCicd)
3079
+
3080
+ | Method | GitHub API | Returns |
3081
+ |--------|-----------|---------|
3082
+ | `listWorkflowRuns({ repo, workflowId })` | `GET /repos/.../actions/runs` or `.../workflows/{id}/runs` | `NormalizedWorkflowRun[]` |
3083
+ | `listJobs({ repo, runId })` | `GET /repos/.../actions/runs/{id}/jobs` | `NormalizedJob[]` |
3084
+ | `rerunWorkflow({ repo, runId })` | `POST /repos/.../actions/runs/{id}/rerun` | `{ triggered, runId }` |
3085
+ | `cancelWorkflow({ repo, runId })` | `POST /repos/.../actions/runs/{id}/cancel` | `{ cancelled, runId }` |
3086
+ | `createCheck({ repo, name, headSha, ... })` | `POST /repos/.../check-runs` | `NormalizedCheckRun` |
3087
+ | `updateCheck({ repo, checkRunId, ... })` | `PATCH /repos/.../check-runs/{id}` | `NormalizedCheckRun` |
3088
+
3089
+ #### Webhook Events Handled
3090
+
3091
+ The webhook controller processes these GitHub event types:
3092
+ - `push` — code pushed to repository
3093
+ - `pull_request` — PR opened, closed, merged, edited, synchronized
3094
+ - `pull_request_review` — review submitted
3095
+ - `issues` — issue opened, closed, edited, labeled
3096
+ - `issue_comment` — comment on issue or PR
3097
+ - `workflow_run` — GitHub Actions workflow started/completed
3098
+ - `workflow_job` — individual job within a workflow
3099
+ - `check_suite` — check suite created/completed
3100
+ - `check_run` — check run created/completed
3101
+ - `deployment` — deployment created
3102
+ - `deployment_status` — deployment status changed
3103
+ - `label` — label created/edited/deleted
3104
+
3105
+ #### Bidirectional Sync Flow
3106
+
3107
+ **Inbound (GitHub → Krate):**
3108
+ 1. GitHub fires webhook to Krate endpoint
3109
+ 2. `webhookController.processDelivery()` — verify HMAC, dedup, queue
3110
+ 3. `syncController.normalizeEvent()` — raw → canonical format
3111
+ 4. `syncController.upsertResource()` — create/update local resource with external envelope
3112
+ 5. `syncController.updateWatermark()` — advance high-watermark
3113
+ 6. Event bus emits resource-change → SSE → UI updates
3114
+
3115
+ **Outbound (Krate → GitHub):**
3116
+ 1. User creates/modifies resource in Krate
3117
+ 2. `writeController.createWriteIntent()` — queue write with idempotency key
3118
+ 3. If approval required: pause at PendingApproval
3119
+ 4. `writeController.executeWriteIntent()` — call GitHub API via adapter
3120
+ 5. On success: mark Succeeded, update ExternalObjectLink
3121
+ 6. On failure: retry up to maxRetries, then mark Failed
3122
+
3123
+ #### Conflict Handling
3124
+
3125
+ When GitHub and Krate disagree on a field value:
3126
+ 1. `conflictController.detectConflict()` — compares localValue vs externalValue
3127
+ 2. If different: creates ExternalSyncConflict (phase: Open)
3128
+ 3. Resolution options:
3129
+ - Auto-resolve via ExternalBackendSyncPolicy conflictResolution setting
3130
+ - Manual resolve via UI/API
3131
+ 4. `conflictController.resolveConflict()` — applies chosen strategy
3132
+ 5. `conflictController.supersededCheck()` — cleans up when new sync arrives
3133
+
3134
+ ---
3135
+
3136
+ ### 2.3 Issue/Project Relationships
3137
+
3138
+ #### How KrateProject Groups Issues
3139
+
3140
+ 1. **Project Definition:**
3141
+ - `spec.workflowColumns[]` defines kanban columns (e.g., Backlog, In Progress, Done)
3142
+ - Each column has: `{ id, displayName, color, default? }`
3143
+ - `spec.repositoryRefs[]` links project to repositories
3144
+
3145
+ 2. **Issue → Project Association:**
3146
+ - Issues reference projects via `spec.projectRefs[]`
3147
+ - Multiple issues can belong to one project
3148
+ - One issue can belong to multiple projects
3149
+
3150
+ 3. **Kanban Board Derivation:**
3151
+ - Board columns come from `project.spec.workflowColumns`
3152
+ - Issues are placed in columns based on `issue.spec.workflowState`
3153
+ - Default column: first column with `default: true`, or first column overall
3154
+
3155
+ 4. **Drag-Drop Updates:**
3156
+ - Moving issue between columns updates `issue.spec.workflowState` to target column ID
3157
+ - Triggers event bus → SSE → UI update
3158
+
3159
+ 5. **External Issues (GitHub → Krate):**
3160
+ - GitHub issues synced via ExternalBackendBinding
3161
+ - External issue gets ExternalObjectLink with nativeId
3162
+ - workflowState mapped from GitHub project column (if synced)
3163
+ - Labels and assignees synchronized bidirectionally
3164
+
3165
+ 6. **Work Item Links:**
3166
+ - `WorkItemSessionLink`: connects issues to agent sessions that worked on them
3167
+ - `WorkItemWorkspaceLink`: connects issues to workspaces containing related work
3168
+ - Enables: "which agent sessions touched this issue?" queries
3169
+
3170
+ ---
3171
+
3172
+ ### 2.4 GitHub Project Sync Plan
3173
+
3174
+ `githubProjectIssueSyncPlan()` produces a plan with these actions:
3175
+ 1. `syncProjectItem` — sync issue to/from GitHub Project board
3176
+ 2. `syncIssueMetadata` — sync labels, assignees, state
3177
+ 3. `syncRepositoryLinks` — sync repository associations
3178
+
3179
+ ---
3180
+
3181
+ ## PART 3: Runs, Runners, and Pipeline Integration
3182
+
3183
+ ---
3184
+
3185
+ ### 3.1 Run Lifecycle (AgentDispatchRun)
3186
+
3187
+ The complete lifecycle of an agent dispatch run:
3188
+
3189
+ #### Phase 1: Initiation
3190
+
3191
+ **Manual Dispatch (`createManualDispatch()`):**
3192
+ 1. **Stack Resolution**: Find AgentStack by name in resources
3193
+ - Error if not found: `stack-not-found`
3194
+
3195
+ 2. **Permission Review**: `permissionReviewer.reviewPermissions()`
3196
+ - Inputs: repository, ref, actor, agentStack, resources
3197
+ - Outcomes:
3198
+ - `allowed` → proceed to workspace provisioning
3199
+ - `denied` → return error with review details
3200
+ - `requires-approval` → create approval, return early
3201
+
3202
+ 3. **Memory Snapshot**: If AgentMemoryRepository exists:
3203
+ - Resolve time-travel (mode: current)
3204
+ - Create AgentMemorySnapshot with resolved commit
3205
+ - Pin memory state for reproducibility
3206
+
3207
+ 4. **Approval Gate** (if requires-approval):
3208
+ - Create AgentApproval (action: 'secret-access')
3209
+ - Create AgentDispatchRun with phase: `AwaitingApproval`
3210
+ - Return early — human must approve before continuing
3211
+
3212
+ **Trigger-Based Dispatch (`processEvent()`):**
3213
+ 1. Evaluate event against all AgentTriggerRule resources
3214
+ 2. For each matching rule (not deduplicated):
3215
+ - Create AgentTriggerExecution record
3216
+ - Call `createManualDispatch()` with rule's agentStack and taskKind
3217
+ 3. Track: processed, dispatched, skipped counts
3218
+
3219
+ #### Phase 2: Workspace Provisioning
3220
+
3221
+ 5. **Find Reusable Workspace**: `findReusableWorkspace()`
3222
+ - Match: same org + same repository + same branch + phase=Ready
3223
+ - If found: `claimWorkspace()` → phase: InUse
3224
+
3225
+ 6. **Create New Workspace** (if no reusable):
3226
+ - `createWorkspace()` → generates:
3227
+ - KrateWorkspace resource (phase: Pending)
3228
+ - PersistentVolumeClaim manifest
3229
+ - PVC: storageClassName=standard, capacity=10Gi, ReadWriteOnce
3230
+
3231
+ 7. **Mount Spec**: `getMountSpec()` → volume + volumeMount for pod spec
3232
+
3233
+ #### Phase 3: Context Assembly
3234
+
3235
+ 8. **Context Bundle**: `assembleContextBundle()`
3236
+ - Gathers: stack spec prompts, context labels, repository info, source refs
3237
+ - Applies: redaction scanning
3238
+ - Produces: immutable bundle with content-addressable digest
3239
+
3240
+ #### Phase 4: Resource Creation
3241
+
3242
+ 9. **Create AgentDispatchRun**:
3243
+ - Spec: organizationRef, repository, sourceRefs, agentStack, taskKind, contextBundleRef
3244
+ - Optional: memorySnapshotRef, workspaceRef, mountSpec
3245
+ - Status: phase=Pending, queuedAt=now
3246
+
3247
+ 10. **Create AgentDispatchAttempt**:
3248
+ - Spec: agentDispatchRun, attemptReason='initial', agentStackSnapshot (frozen)
3249
+ - Status: permissionSnapshot, queueEnteredAt
3250
+
3251
+ #### Phase 5: Session Launch
3252
+
3253
+ 11. **Agent Mux Client Check**: `agentMuxClient.isAvailable()`
3254
+ - If unavailable: phase=Queued, condition `AgentMuxBound: False (Unavailable)`
3255
+
3256
+ 12. **Launch Session**: `agentMuxClient.launchSession({ stack, contextBundle, permissionSnapshot })`
3257
+ - Success: returns `{ runId, sessionId }`
3258
+ - Sets: attempt.status.agentMuxRunId, agentMuxSessionId
3259
+ - Run phase → Running, attempt.status.startedAt
3260
+
3261
+ 13. **SSE Subscription**: `agentMuxClient.subscribeToEvents(runId, handler)`
3262
+ - Streams real-time events from Agent Mux
3263
+ - Events collected in array for transcript reconciliation
3264
+ - Run status: `sseSubscription: { runId, active: true }`
3265
+
3266
+ 14. **Transcript Creation**: `agentMuxClient.reconcileTranscript(sessionId, events)`
3267
+ - Creates AgentSessionTranscript resource
3268
+ - Run status: transcriptRef set
3269
+
3270
+ #### Phase 6: Completion
3271
+
3272
+ 15. **Success**: Agent completes task
3273
+ - Run phase → Succeeded
3274
+ - Workspace released: `releaseWorkspace()` → phase: Ready
3275
+ - Artifacts emitted as KrateArtifact resources
3276
+
3277
+ 16. **Failure**: Agent fails or times out
3278
+ - Run phase → Failed
3279
+ - May create new attempt (retry) with attemptReason='retry'
3280
+ - Workspace may be retained for debugging
3281
+
3282
+ ---
3283
+
3284
+ ### 3.2 Runner System
3285
+
3286
+ #### RunnerPool Resource Management
3287
+
3288
+ **Pool Validation:**
3289
+ - metadata.name: required
3290
+ - organizationRef: required, non-empty
3291
+ - warmReplicas: non-negative integer
3292
+ - maxReplicas: positive integer, >= warmReplicas
3293
+
3294
+ **Pool Status Tracking:**
3295
+ ```
3296
+ { poolName, idle, active, terminating, total, desired, maxReplicas, phase, scaling }
3297
+ ```
3298
+ - Phase: Empty (no runners) | Active (runners executing) | Idle (runners waiting)
3299
+ - Scaling: ScalingUp (total < desired) | ScalingDown (total > max) | Stable
3300
+
3301
+ **Capacity Tracking:**
3302
+ ```
3303
+ { poolName, maxReplicas, used, available, utilizationPct }
3304
+ ```
3305
+ - used: runners with status=Running
3306
+ - available: maxReplicas - used
3307
+ - utilizationPct: (used/maxReplicas) * 100
3308
+
3309
+ #### Runner Lifecycle
3310
+
3311
+ **Creation (`createRunner()`):**
3312
+ - Generates unique ID: `runner-{poolName}-{timestamp}-{random}`
3313
+ - Status: `Idle` (pre-warmed) or `Running` (assigned to job)
3314
+ - Produces pod spec for Kubernetes
3315
+
3316
+ **Termination (`terminateRunner()`):**
3317
+ - Status → Terminating
3318
+ - Removes job assignment
3319
+ - Removes from registry
3320
+
3321
+ **Job Scheduling (`scheduleJob()`):**
3322
+ 1. Check if job already assigned → return existing runner (reused)
3323
+ 2. Find idle runner in pool → assign job, status → Running
3324
+ 3. Check capacity → if available, create new runner
3325
+ 4. No capacity → error: `no-capacity`
3326
+
3327
+ #### Pod Spec Generation
3328
+
3329
+ `generatePodSpec()` produces:
3330
+ ```yaml
3331
+ apiVersion: v1
3332
+ kind: Pod
3333
+ metadata:
3334
+ name: runner-{runnerId}
3335
+ namespace: {org namespace}
3336
+ labels:
3337
+ krate.a5c.ai/runner: {runnerId}
3338
+ krate.a5c.ai/pool: {poolName}
3339
+ krate.a5c.ai/org: {orgRef}
3340
+ spec:
3341
+ serviceAccountName: {configured or 'krate-runner'}
3342
+ restartPolicy: Never
3343
+ containers:
3344
+ - name: runner
3345
+ image: {pool.spec.image or 'ubuntu:24.04'}
3346
+ env:
3347
+ - name: KRATE_ORG
3348
+ value: {organizationRef}
3349
+ - name: KRATE_RUN_ID
3350
+ value: {runId}
3351
+ - name: KRATE_WORKSPACE_PATH
3352
+ value: /workspace
3353
+ volumeMounts:
3354
+ - name: workspace
3355
+ mountPath: /workspace
3356
+ resources:
3357
+ limits: {cpu: '2', memory: '4Gi'}
3358
+ requests: {cpu: '500m', memory: '1Gi'}
3359
+ volumes:
3360
+ - name: workspace
3361
+ persistentVolumeClaim:
3362
+ claimName: krate-ws-{runId}
3363
+ ```
3364
+
3365
+ ---
3366
+
3367
+ ### 3.3 Argo CD / KubeVela Relationship
3368
+
3369
+ #### Argo CD for GitOps Deployment
3370
+
3371
+ Krate uses Argo CD for GitOps-based deployment of itself. The integration is in
3372
+ `argocd-gitops.js`.
3373
+
3374
+ **Application Resource (`createArgoCdApplication()`):**
3375
+ ```yaml
3376
+ apiVersion: argoproj.io/v1alpha1
3377
+ kind: Application
3378
+ metadata:
3379
+ name: krate
3380
+ namespace: argocd
3381
+ labels:
3382
+ app.kubernetes.io/part-of: krate
3383
+ krate.a5c.ai/gitops-engine: argocd
3384
+ spec:
3385
+ project: default
3386
+ source:
3387
+ repoURL: {configured repo URL}
3388
+ targetRevision: HEAD
3389
+ path: charts/krate
3390
+ destination:
3391
+ server: https://kubernetes.default.svc
3392
+ namespace: krate-system
3393
+ syncPolicy:
3394
+ automated:
3395
+ prune: true
3396
+ selfHeal: true
3397
+ syncOptions:
3398
+ - CreateNamespace=true
3399
+ ```
3400
+
3401
+ **GitOps Plan (`createKrateGitOpsPlan()`):**
3402
+ - Engine: argocd
3403
+ - Required cluster resources: Application.argoproj.io, Namespace, ServiceAccount, RBAC, APIService, Krate CRDs
3404
+ - Sync guarantees: automated prune, automated selfHeal, namespace creation
3405
+
3406
+ **Label Convention:**
3407
+ - `krate.a5c.ai/gitops-engine: argocd` — identifies GitOps-managed resources
3408
+
3409
+ #### KubeVela for Delivery Abstractions
3410
+
3411
+ KubeVela provides OAM (Open Application Model) delivery abstractions:
3412
+
3413
+ **Discovered Resources (core.oam.dev group):**
3414
+ - KubeVelaApplication — OAM application definition
3415
+ - KubeVelaApplicationRevision — revision history
3416
+ - KubeVelaComponentDefinition — component type definitions
3417
+ - KubeVelaWorkloadDefinition — workload type definitions
3418
+ - KubeVelaTraitDefinition — trait type definitions
3419
+ - KubeVelaScopeDefinition — scope type definitions
3420
+ - KubeVelaPolicyDefinition — policy type definitions
3421
+ - KubeVelaPolicy — policy instances
3422
+ - KubeVelaWorkflowStepDefinition — workflow step definitions
3423
+ - KubeVelaWorkflow — workflow instances
3424
+ - KubeVelaResourceTracker — resource tracking (cluster-scoped)
3425
+
3426
+ **KubeVela Integration Points:**
3427
+ - Default namespace: `vela-system` (configurable via `KRATE_KUBEVELA_NAMESPACE`)
3428
+ - Discovered via CRD listing during snapshot
3429
+ - ResourceTracker is cluster-scoped (not namespaced)
3430
+ - Applications are org-scoped (live in org namespaces)
3431
+
3432
+ **When KubeVela is NOT installed:**
3433
+ - Deployments page shows fallback pipeline visualization
3434
+ - No OAM resources in snapshot
3435
+ - CRD discovery reports empty for core.oam.dev group
3436
+
3437
+ **When Kyverno is NOT installed:**
3438
+ - Policies page shows informational banner
3439
+ - No Kyverno resources in snapshot
3440
+ - PolicyBinding enforce mode may be blocked (if `KRATE_KYVERNO_REQUIRE_FOR_ENFORCE_MODE=true`)
3441
+
3442
+ ---
3443
+
3444
+ ### 3.4 External Pipeline Integration
3445
+
3446
+ #### Pipeline/Job ↔ External CI Systems
3447
+
3448
+ **GitHub Actions Integration via ExternalBackendBinding:**
3449
+
3450
+ 1. **Webhook Events:**
3451
+ - `workflow_run` → Pipeline resource projection
3452
+ - `workflow_job` → Job resource projection
3453
+ - `check_suite` → Pipeline status update
3454
+ - `check_run` → Job status update
3455
+
3456
+ 2. **Event Flow:**
3457
+ ```
3458
+ GitHub webhook → ExternalWebhookDelivery → normalizeEvent() → Pipeline/Job upsert
3459
+ ```
3460
+
3461
+ 3. **Pipeline Phase Mapping:**
3462
+ - GitHub `queued` → Pipeline phase `Queued`
3463
+ - GitHub `in_progress` → Pipeline phase `Running`
3464
+ - GitHub `completed` + conclusion `success` → Pipeline phase `Succeeded`
3465
+ - GitHub `completed` + conclusion `failure` → Pipeline phase `Failed`
3466
+
3467
+ 4. **Job Phase Mapping:**
3468
+ - Similar to Pipeline but at individual job level
3469
+ - Tracks: startedAt, completedAt, conclusion
3470
+
3471
+ **Check Run Integration:**
3472
+ - `GitHubCicd.createCheck()` — create check run on commit
3473
+ - `GitHubCicd.updateCheck()` — update check status/conclusion
3474
+ - Used by: Krate to report agent dispatch status back to GitHub
3475
+
3476
+ **Runner Integration (External Runners):**
3477
+ - GitHub Actions self-hosted runners (ARC) can connect to RunnerPool
3478
+ - External runners register via RunnerPool configuration
3479
+ - Trust tier: `trusted` (internal runners) vs `untrusted` (fork runners)
3480
+ - Capacity tracked: external runners count toward pool maxReplicas
3481
+
3482
+ #### Pipeline Visualization (RunnerScheduler)
3483
+
3484
+ `RunnerScheduler.startPipeline()`:
3485
+ - Creates Pipeline resource with ordered steps
3486
+ - Creates Job per step with service account isolation
3487
+ - Jobs start sequentially (first Running, rest Pending)
3488
+ - Trust tier propagated to job labels
3489
+
3490
+ `RunnerScheduler.rerunFromStep()`:
3491
+ - Creates new pipeline named `{original}-rerun-{step}`
3492
+ - Preserves same steps but resumes from specified step
3493
+ - Maintains trust tier from original pipeline
3494
+
3495
+ ---
3496
+
3497
+ ## PART 4: Cross-cutting Relationships
3498
+
3499
+ ---
3500
+
3501
+ ### 4.1 Resource Dependency Graph
3502
+
3503
+ #### AgentStack References (hub resource)
3504
+ ```
3505
+ AgentStack
3506
+ ├── AgentSubagent[] (via subagentRefs)
3507
+ ├── AgentToolProfile (via toolPolicy/toolPolicyRef)
3508
+ ├── AgentMcpServer[] (via mcpServerRefs)
3509
+ ├── AgentSkill[] (via skillRefs)
3510
+ ├── AgentContextLabel[] (via contextLabelRefs)
3511
+ ├── AgentServiceAccount (via runtimeIdentity)
3512
+ ├── KrateWorkspacePolicy (via workspacePolicy)
3513
+ ├── AgentProviderConfig (via provider)
3514
+ └── AgentAdapter (via adapter)
3515
+ ```
3516
+
3517
+ #### AgentDispatchRun References
3518
+ ```
3519
+ AgentDispatchRun
3520
+ ├── AgentStack (via agentStack)
3521
+ ├── Repository (via repository)
3522
+ ├── KrateWorkspace (via workspaceRef)
3523
+ ├─��� AgentContextBundle (via contextBundleRef)
3524
+ ├── AgentMemorySnapshot (via memorySnapshotRef)
3525
+ ├── AgentDispatchAttempt[] (child resources)
3526
+ ├── AgentSession (child, via Attempt)
3527
+ ├── AgentApproval[] (gating resources)
3528
+ ├── KrateArtifact[] (outputs)
3529
+ └── AgentSessionTranscript (via transcriptRef)
3530
+ ```
3531
+
3532
+ #### AgentSession References
3533
+ ```
3534
+ AgentSession
3535
+ ├── AgentDispatchRun (via dispatchRun)
3536
+ ├── AgentSessionTranscript (1:1)
3537
+ ├── AgentSessionAttachment[] (0:N)
3538
+ ├── WorkItemSessionLink[] (to Issues/PRs)
3539
+ └── KrateWorkspace (bound workspace)
3540
+ ```
3541
+
3542
+ #### ExternalBackendBinding References
3543
+ ```
3544
+ ExternalBackendBinding
3545
+ ├── ExternalBackendProvider (via providerRef)
3546
+ ├── ExternalBackendSyncPolicy (controls sync)
3547
+ ├── ExternalWebhookDelivery[] (inbound events)
3548
+ ├── ExternalSyncEvent[] (normalized events)
3549
+ ├── ExternalSyncState[] (watermarks)
3550
+ ├── ExternalWriteIntent[] (outbound writes)
3551
+ ├── ExternalSyncConflict[] (detected conflicts)
3552
+ ├── ExternalObjectLink[] (identity mappings)
3553
+ └── Repository (sync target)
3554
+ ```
3555
+
3556
+ #### KrateProject References
3557
+ ```
3558
+ KrateProject
3559
+ ├── Issue[] (via issue.projectRefs)
3560
+ ├── Repository[] (via repositoryRefs)
3561
+ ├── AgentStack[] (via stackRefs)
3562
+ └── ExternalBackendBinding (for GitHub Projects sync)
3563
+ ```
3564
+
3565
+ #### Pipeline References
3566
+ ```
3567
+ Pipeline
3568
+ ├── Repository (via repository)
3569
+ ├── Job[] (child resources)
3570
+ ├���─ RunnerPool (scheduling target)
3571
+ └── ExternalObjectLink (GitHub Actions workflow_run)
3572
+ ```
3573
+
3574
+ ---
3575
+
3576
+ ### 4.2 Namespace Topology
3577
+
3578
+ #### Platform Namespace (`krate-system`)
3579
+ Contains platform-scoped resources that span all organizations:
3580
+ - Organization (all org definitions)
3581
+ - OrgNamespaceBinding (all namespace bindings)
3582
+
3583
+ #### Organization Namespace (`krate-org-{slug}`)
3584
+ Contains all org-scoped resources:
3585
+ - Identity: User, Team, Invite, IdentityMapping, AuthProvider, AgentServiceAccount
3586
+ - Repository: Repository, SSHKey, RepositoryPermission, BranchProtection, RefPolicy, WebhookSubscription
3587
+ - Agents: AgentStack, AgentSubagent, AgentToolProfile, AgentMcpServer, AgentSkill, AgentTriggerRule, AgentContextLabel, KrateWorkspacePolicy, AgentAdapter, AgentTransportBinding, AgentProviderConfig, KrateProject, AgentGatewayConfig
3588
+ - Memory: AgentMemoryRepository, AgentMemorySource, AgentMemoryOntology, AgentMemoryAssociation
3589
+ - Workspace: KrateWorkspace
3590
+ - External: ExternalBackendProvider, ExternalBackendBinding, ExternalBackendSyncPolicy, ExternalProviderCapabilityManifest
3591
+ - Policy: PolicyProfile, PolicyTemplate, PolicyBinding, PolicyExceptionRequest
3592
+ - Runners: RunnerPool
3593
+ - UI: View, Selector
3594
+ - Aggregated (postgres): PullRequest, Issue, Review, Pipeline, Job, WebhookDelivery, all Agent* aggregated kinds, all External* aggregated kinds
3595
+
3596
+ #### Cross-Namespace References
3597
+ - Resources do NOT reference across org namespaces (org isolation guarantee)
3598
+ - Platform-scoped resources (Organization, OrgNamespaceBinding) are in `krate-system`
3599
+ - Org-scoped resources resolve their namespace via `resolveResourceOrg()`
3600
+ - If `metadata.namespace` conflicts with org namespace → error thrown
3601
+ - If org label conflicts with spec.organizationRef → error thrown
3602
+
3603
+ #### Org Isolation Guarantees
3604
+ - Each org gets exactly one namespace
3605
+ - Resources cannot reference resources in other org namespaces
3606
+ - Snapshot enumeration only queries known org namespaces
3607
+ - `withOrgScope()` enforces namespace consistency at apply time
3608
+ - RBAC via AgentRoleBinding is scoped to org namespace
3609
+
3610
+ ---
3611
+
3612
+ ### 4.3 Event Propagation
3613
+
3614
+ #### Resource Change → UI Update
3615
+ ```
3616
+ Resource apply/delete
3617
+ → event-bus.emitResourceChange(kind, name, operation)
3618
+ → globalEventBus.emit({ type: 'resource-change', kind, name, operation, timestamp })
3619
+ → SSE endpoint streams to connected clients
3620
+ → React UI receives event → refetches affected resources
3621
+ ```
3622
+
3623
+ #### Webhook → Resource Update → UI
3624
+ ```
3625
+ External webhook (GitHub, etc.)
3626
+ → webhookController.processDelivery() [verify HMAC, dedup]
3627
+ → syncController.normalizeEvent() [raw → canonical]
3628
+ → syncController.upsertResource() [create/update with envelope]
3629
+ → syncController.updateWatermark() [advance cursor]
3630
+ → event-bus.emitResourceChange()
3631
+ → SSE → UI update
3632
+ ```
3633
+
3634
+ #### Trigger Rule → Dispatch → Session → Events
3635
+ ```
3636
+ Event arrives (push, PR, issue, cron, webhook, comment, label)
3637
+ → triggerController.evaluateEvent() [match rules, dedup]
3638
+ → triggerController.createTriggerExecution() [audit record]
3639
+ → dispatchController.createManualDispatch() [full orchestration]
3640
+ → agentMuxClient.launchSession() [start agent]
3641
+ → agentMuxClient.subscribeToEvents() [SSE from agent]
3642
+ → agentMuxClient.reconcileTranscript() [build transcript]
3643
+ → event-bus.emitResourceChange('AgentDispatchRun', ...)
3644
+ → SSE → UI shows running session
3645
+ ```
3646
+
3647
+ #### Approval Request → User Action → Run Continues
3648
+ ```
3649
+ Permission review says 'requires-approval'
3650
+ → approvalController.createApprovalRequest() [phase: Pending]
3651
+ → event-bus.emitResourceChange('AgentApproval', ...)
3652
+ → SSE → UI shows approval request notification
3653
+ → User clicks approve/deny in UI
3654
+ → approvalController.recordDecision() [phase: Approved/Denied]
3655
+ → event-bus.emitResourceChange('AgentApproval', ...)
3656
+ → If approved: dispatch continues from step 5 (workspace provisioning)
3657
+ → If denied: run marked Failed
3658
+ ```
3659
+
3660
+ #### External Write → Conflict → Resolution
3661
+ ```
3662
+ User modifies resource in Krate
3663
+ → writeController.createWriteIntent() [queue outbound write]
3664
+ → If requiresApproval: pause at PendingApproval
3665
+ → writeController.executeWriteIntent() [call external API]
3666
+ → On conflict: conflictController.detectConflict()
3667
+ → ExternalSyncConflict created (phase: Open)
3668
+ → event-bus → SSE → UI shows conflict
3669
+ → User resolves via UI
3670
+ → conflictController.resolveConflict() [strategy applied]
3671
+ → If prefer-krate: retry write
3672
+ → If prefer-external: accept external value, update local
3673
+ ```
3674
+
3675
+ ---
3676
+
3677
+ ### 4.4 Storage Boundaries
3678
+
3679
+ | Storage | Resources | Access Pattern |
3680
+ |---------|-----------|----------------|
3681
+ | etcd (CRDs) | Organization, User, Team, Repository, AgentStack, ExternalBackendProvider, etc. (44 kinds) | kubectl get/apply/delete, K8s watch |
3682
+ | postgres (Aggregated) | PullRequest, Issue, Pipeline, AgentDispatchRun, AgentSession, ExternalWebhookDelivery, etc. (30 kinds) | Aggregated API, snapshot cache |
3683
+ | kubevela | KubeVelaApplication, etc. (12 kinds) | kubectl via core.oam.dev group |
3684
+ | kyverno | KyvernoPolicy, PolicyReport, etc. (10 kinds) | kubectl via kyverno.io group |
3685
+ | core | Secret, ConfigMap | kubectl (not in snapshot, on-demand access) |
3686
+ | repositories | Git repository data | Gitea API, raw git |
3687
+ | objects | Artifacts, attachments | Object storage (referenced by digest) |
3688
+
3689
+ ---
3690
+
3691
+ ### 4.5 Snapshot Architecture
3692
+
3693
+ The `getControllerSnapshot()` function produces a comprehensive cluster state:
3694
+
3695
+ ```javascript
3696
+ {
3697
+ source: 'kubernetes',
3698
+ mode: 'kubernetes-api',
3699
+ namespace, // platform namespace
3700
+ generatedAt, // ISO timestamp
3701
+ correlationId, // UUID for request tracing
3702
+ kubectl: { // kubectl binary status
3703
+ binary, context, clientVersion, available, errors
3704
+ },
3705
+ apiService, // Krate APIService resource (if exists)
3706
+ crds, // Discovered CRD resources
3707
+ resources: { // All resources by kind
3708
+ Organization: [...],
3709
+ Repository: [...],
3710
+ AgentStack: [...],
3711
+ // ... all 76+ kinds
3712
+ },
3713
+ kyverno: { // Kyverno discovery
3714
+ mode, namespace, detected, controllers, resources, reports, permissions, degraded
3715
+ },
3716
+ events, // K8s events in platform namespace
3717
+ permissions, // RBAC can-i results per resource kind
3718
+ storage, // Storage boundary descriptions
3719
+ commands // kubectl command templates per kind
3720
+ }
3721
+ ```
3722
+
3723
+ **Snapshot Enumeration:**
3724
+ 1. Platform-scoped resources: listed in `krate-system`
3725
+ 2. Org-scoped resources: listed in each discovered org namespace
3726
+ 3. Kyverno resources: listed if CRDs detected
3727
+ 4. Stale-while-revalidate: 30s TTL cache
3728
+
3729
+ **In-Cluster Detection:**
3730
+ - Checks `KUBERNETES_SERVICE_HOST` + service account token
3731
+ - Auto-configures kubectl with in-cluster credentials
3732
+ - Falls back to kubeconfig if not in-cluster