@a5c-ai/kradle 5.0.1-staging.3abdf9534c25

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