@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,161 @@
1
+ // External Provider Adapter — Slice 3.2
2
+ // Defines the ExternalProviderAdapter contract and ProviderRegistry.
3
+ //
4
+ // Each provider (GitHub, GitLab, Gitea, etc.) implements this interface to
5
+ // integrate with the Kradle platform.
6
+
7
+ /**
8
+ * Valid optional interface keys that a provider adapter may implement.
9
+ */
10
+ const VALID_INTERFACES = ['issueTracking', 'cicd', 'gitForge'];
11
+
12
+ /**
13
+ * Valid health status values returned by health().
14
+ */
15
+ const VALID_HEALTH_STATUSES = ['healthy', 'degraded', 'unavailable'];
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Provider Registry
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /**
22
+ * Create a provider registry that stores and retrieves ExternalProviderAdapter
23
+ * instances by provider type string.
24
+ *
25
+ * @returns {{ register(type: string, adapter: object): void, get(type: string): object|null, list(): string[] }}
26
+ */
27
+ export function createProviderRegistry() {
28
+ /** @type {Map<string, object>} */
29
+ const adapters = new Map();
30
+
31
+ return {
32
+ /**
33
+ * Register a provider adapter under the given type key.
34
+ * Re-registering the same type replaces the existing adapter.
35
+ * @param {string} type
36
+ * @param {object} adapter
37
+ */
38
+ register(type, adapter) {
39
+ adapters.set(type, adapter);
40
+ },
41
+
42
+ /**
43
+ * Retrieve a registered adapter by provider type, or null if not found.
44
+ * @param {string} type
45
+ * @returns {object|null}
46
+ */
47
+ get(type) {
48
+ return adapters.has(type) ? adapters.get(type) : null;
49
+ },
50
+
51
+ /**
52
+ * List all registered provider type keys.
53
+ * @returns {string[]}
54
+ */
55
+ list() {
56
+ return [...adapters.keys()];
57
+ }
58
+ };
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Adapter validation
63
+ // ---------------------------------------------------------------------------
64
+
65
+ /**
66
+ * Validate an ExternalProviderAdapter object against the contract.
67
+ *
68
+ * Required contract:
69
+ * - descriptor() → { providerType, displayName, hosting, authModes, apiCapabilities }
70
+ * - health() → { status: 'healthy'|'degraded'|'unavailable', message }
71
+ * - at least one of: issueTracking, cicd, gitForge
72
+ * - normalizeWebhook(payload) → NormalizedEvent[]
73
+ * - verifyWebhook(request) → { valid, reason }
74
+ *
75
+ * @param {object} adapter
76
+ * @returns {{ valid: boolean, errors: string[] }}
77
+ */
78
+ export function validateProviderAdapter(adapter) {
79
+ const errors = [];
80
+
81
+ if (adapter == null) {
82
+ errors.push('adapter must not be null or undefined');
83
+ return { valid: false, errors };
84
+ }
85
+
86
+ // descriptor must be a function
87
+ if (typeof adapter.descriptor !== 'function') {
88
+ errors.push('adapter.descriptor must be a function returning { providerType, displayName, hosting, authModes }');
89
+ }
90
+
91
+ // health must be a function
92
+ if (typeof adapter.health !== 'function') {
93
+ errors.push('adapter.health must be a function returning { status, message }');
94
+ }
95
+
96
+ // At least one optional interface must be present
97
+ const presentInterfaces = VALID_INTERFACES.filter((key) => adapter[key] != null);
98
+ if (presentInterfaces.length === 0) {
99
+ errors.push(
100
+ `adapter must implement at least one provider interface: ${VALID_INTERFACES.join(', ')} (issueTracking, cicd, or gitForge)`
101
+ );
102
+ }
103
+
104
+ // normalizeWebhook must be a function
105
+ if (typeof adapter.normalizeWebhook !== 'function') {
106
+ errors.push('adapter.normalizeWebhook must be a function(payload) → NormalizedEvent[]');
107
+ }
108
+
109
+ // verifyWebhook must be a function
110
+ if (typeof adapter.verifyWebhook !== 'function') {
111
+ errors.push('adapter.verifyWebhook must be a function(request) → { valid, reason }');
112
+ }
113
+
114
+ return { valid: errors.length === 0, errors };
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Capability manifest validation
119
+ // ---------------------------------------------------------------------------
120
+
121
+ /**
122
+ * Validate a capability manifest object.
123
+ *
124
+ * A capability manifest declares which provider interfaces a concrete adapter
125
+ * supports, so the platform can route requests accordingly.
126
+ *
127
+ * Required shape:
128
+ * { providerType: string, interfaces: string[] }
129
+ *
130
+ * @param {object} manifest
131
+ * @returns {{ valid: boolean, errors: string[] }}
132
+ */
133
+ export function validateCapabilityManifest(manifest) {
134
+ const errors = [];
135
+
136
+ if (manifest == null) {
137
+ errors.push('manifest must not be null or undefined');
138
+ return { valid: false, errors };
139
+ }
140
+
141
+ // providerType is required
142
+ if (!manifest.providerType || typeof manifest.providerType !== 'string') {
143
+ errors.push('manifest.providerType is required and must be a non-empty string');
144
+ }
145
+
146
+ // interfaces must be a non-empty array of known interface names
147
+ if (!Array.isArray(manifest.interfaces) || manifest.interfaces.length === 0) {
148
+ errors.push(
149
+ `manifest.interfaces must be a non-empty array; supported interfaces are: ${VALID_INTERFACES.join(', ')}`
150
+ );
151
+ } else {
152
+ const unknown = manifest.interfaces.filter((iface) => !VALID_INTERFACES.includes(iface));
153
+ if (unknown.length > 0) {
154
+ errors.push(
155
+ `manifest.interfaces contains unknown or invalid interface(s): ${unknown.join(', ')}; valid interfaces are: ${VALID_INTERFACES.join(', ')}`
156
+ );
157
+ }
158
+ }
159
+
160
+ return { valid: errors.length === 0, errors };
161
+ }
@@ -0,0 +1,221 @@
1
+ // External Provider Resource Factory — Slice B3+D1
2
+ //
3
+ // Provides:
4
+ // - createDefaultProviderRegistry() — auto-registers GitHub provider
5
+ // - createTypedProvider() — creates typed provider CRD resources (GitProvider, CiProvider, etc.)
6
+ // - createExternalBackendProvider() — DEPRECATED, use createTypedProvider()
7
+
8
+ import { createProviderRegistry } from './provider-adapter.js';
9
+ import { GitHubGitForge } from './github/git-forge.js';
10
+ import { GitHubIssueTracking } from './github/issue-tracking.js';
11
+ import { GitHubCicd } from './github/cicd.js';
12
+
13
+ // Re-export for consumers that want the real classes from this module
14
+ export { GitHubGitForge, GitHubIssueTracking, GitHubCicd };
15
+
16
+ // Valid typed provider kinds
17
+ const TYPED_PROVIDER_KINDS = new Set([
18
+ 'GitProvider',
19
+ 'CiProvider',
20
+ 'IssueTrackerProvider',
21
+ 'AppHostingProvider',
22
+ 'ArtifactRegistryProvider',
23
+ ]);
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // GitHub provider descriptor — wraps real GitHub classes
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * Build a GitHub provider adapter descriptor for the registry.
31
+ * This descriptor exposes real GitHubGitForge, GitHubIssueTracking, and
32
+ * GitHubCicd classes as factory methods. Credential-free listing operations
33
+ * return empty arrays; authenticated operations are created via createGitHubProvider().
34
+ *
35
+ * @returns {object} ExternalProviderAdapter descriptor
36
+ */
37
+ function buildGitHubAdapterDescriptor() {
38
+ return {
39
+ descriptor() {
40
+ return {
41
+ providerType: 'github',
42
+ displayName: 'GitHub',
43
+ hosting: ['cloud', 'self-hosted'],
44
+ authModes: ['github-app', 'pat'],
45
+ interfaces: ['gitForge', 'issueTracking', 'cicd']
46
+ };
47
+ },
48
+
49
+ health() {
50
+ return { status: 'healthy', message: 'GitHub provider registered (no live connection)' };
51
+ },
52
+
53
+ /**
54
+ * Create a GitHubGitForge instance bound to specific credentials.
55
+ *
56
+ * @param {{ owner: string, installationToken: string, fetchImpl?: Function }} opts
57
+ * @returns {GitHubGitForge}
58
+ */
59
+ createForge({ owner, installationToken, fetchImpl } = {}) {
60
+ return new GitHubGitForge({ owner, installationToken, fetchImpl: fetchImpl ?? globalThis.fetch });
61
+ },
62
+
63
+ /**
64
+ * Create a GitHubIssueTracking instance bound to specific credentials.
65
+ *
66
+ * @param {{ owner: string, installationToken: string, fetchImpl?: Function }} opts
67
+ * @returns {GitHubIssueTracking}
68
+ */
69
+ createIssueTracker({ owner, installationToken, fetchImpl } = {}) {
70
+ return new GitHubIssueTracking({ owner, installationToken, fetchImpl: fetchImpl ?? globalThis.fetch });
71
+ },
72
+
73
+ /**
74
+ * Create a GitHubCicd instance bound to specific credentials.
75
+ *
76
+ * @param {{ owner: string, installationToken: string, fetchImpl?: Function }} opts
77
+ * @returns {GitHubCicd}
78
+ */
79
+ createCicd({ owner, installationToken, fetchImpl } = {}) {
80
+ return new GitHubCicd({ owner, installationToken, fetchImpl: fetchImpl ?? globalThis.fetch });
81
+ },
82
+
83
+ // Credential-free listing stubs — real operations require authenticated instances
84
+ gitForge: {
85
+ listRepositories: async () => [],
86
+ createRepository: async () => { throw new Error('Use createForge() for authenticated git forge operations'); }
87
+ },
88
+
89
+ issueTracking: {
90
+ listIssues: async () => [],
91
+ createIssue: async () => { throw new Error('Use createIssueTracker() for authenticated issue tracking operations'); }
92
+ },
93
+
94
+ cicd: {
95
+ listWorkflowRuns: async () => [],
96
+ triggerWorkflow: async () => { throw new Error('Use createCicd() for authenticated CI/CD operations'); }
97
+ },
98
+
99
+ normalizeWebhook(payload) {
100
+ // Passthrough — real normalization lives in the GitHub webhook controller
101
+ return [{ type: payload?.action ?? 'unknown', payload }];
102
+ },
103
+
104
+ verifyWebhook(_request) {
105
+ // Default: unverified; real HMAC verification is in webhook-controller
106
+ return { valid: false, reason: 'use webhook-controller for HMAC verification' };
107
+ }
108
+ };
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // D1: createDefaultProviderRegistry
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /**
116
+ * Create a provider registry pre-loaded with the GitHub provider.
117
+ *
118
+ * @returns {object} ProviderRegistry with github auto-registered
119
+ */
120
+ export function createDefaultProviderRegistry() {
121
+ const registry = createProviderRegistry();
122
+ registry.register('github', buildGitHubAdapterDescriptor());
123
+ return registry;
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // D1: createTypedProvider
128
+ // ---------------------------------------------------------------------------
129
+
130
+ /**
131
+ * Create a typed provider CRD resource (GitProvider, CiProvider, etc.).
132
+ * Used by the external backend wizard to persist provider registrations.
133
+ *
134
+ * @param {string} kind - One of GitProvider, CiProvider, IssueTrackerProvider, AppHostingProvider, ArtifactRegistryProvider
135
+ * @param {string} platform - Platform identifier (e.g. 'github', 'gitlab', 'vercel')
136
+ * @param {{ name?: string, namespace?: string, organizationRef?: string, endpoint?: string, secretRef?: string }} spec
137
+ * @returns {object} K8s-style typed provider resource
138
+ */
139
+ export function createTypedProvider(kind, platform, {
140
+ name,
141
+ namespace = 'default',
142
+ organizationRef = 'default',
143
+ endpoint = '',
144
+ secretRef = '',
145
+ } = {}) {
146
+ if (!kind) throw new Error('createTypedProvider: kind is required');
147
+ if (!TYPED_PROVIDER_KINDS.has(kind)) throw new Error(`createTypedProvider: unknown kind "${kind}", expected one of ${[...TYPED_PROVIDER_KINDS].join(', ')}`);
148
+ if (!platform) throw new Error('createTypedProvider: platform is required');
149
+ const resourceName = name || `${platform}-${kind.toLowerCase()}`;
150
+
151
+ const now = new Date().toISOString();
152
+
153
+ return {
154
+ apiVersion: 'kradle.a5c.ai/v1alpha1',
155
+ kind,
156
+ metadata: {
157
+ name: resourceName,
158
+ namespace,
159
+ labels: {},
160
+ annotations: {}
161
+ },
162
+ spec: {
163
+ organizationRef,
164
+ platform,
165
+ endpoint,
166
+ ...(secretRef ? { secretRef } : {}),
167
+ },
168
+ status: {
169
+ phase: 'Pending',
170
+ createdAt: now
171
+ }
172
+ };
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // D1: createExternalBackendProvider (DEPRECATED — use createTypedProvider)
177
+ // ---------------------------------------------------------------------------
178
+
179
+ /**
180
+ * @deprecated Use createTypedProvider() instead.
181
+ * Create an ExternalBackendProvider CRD resource.
182
+ * Retained for backward compatibility. Maps to createTypedProvider('GitProvider', ...).
183
+ *
184
+ * @param {{ name: string, namespace?: string, providerType: string,
185
+ * displayName: string, config?: object, organizationRef?: string }} opts
186
+ * @returns {object} K8s-style provider resource
187
+ */
188
+ export function createExternalBackendProvider({
189
+ name,
190
+ namespace = 'default',
191
+ providerType,
192
+ displayName,
193
+ config = {},
194
+ organizationRef = 'default'
195
+ } = {}) {
196
+ if (!name) throw new Error('createExternalBackendProvider: name is required');
197
+ if (!providerType) throw new Error('createExternalBackendProvider: providerType is required');
198
+
199
+ const now = new Date().toISOString();
200
+
201
+ return {
202
+ apiVersion: 'kradle.a5c.ai/v1alpha1',
203
+ kind: 'ExternalBackendProvider',
204
+ metadata: {
205
+ name,
206
+ namespace,
207
+ labels: {},
208
+ annotations: {}
209
+ },
210
+ spec: {
211
+ organizationRef,
212
+ providerType,
213
+ displayName: displayName || providerType,
214
+ config: { ...config }
215
+ },
216
+ status: {
217
+ phase: 'Pending',
218
+ createdAt: now
219
+ }
220
+ };
221
+ }
@@ -0,0 +1,235 @@
1
+ // External Sync Controller — Slice 3.4
2
+ //
3
+ // Manages bidirectional sync between external providers and the Kradle resource store.
4
+ //
5
+ // Responsibilities:
6
+ // - Event normalization (raw provider event → canonical internal format)
7
+ // - Resource upsert with external identity envelope (nativeId, url, etag)
8
+ // - High-watermark tracking per binding
9
+ // - Ownership mode arbitration (bidirectional / external-owned / kradle-owned)
10
+ // - Tombstone creation for deleted external resources
11
+
12
+ export const SYNC_CONTROLLER_BOUNDARY = {
13
+ role: 'sync-controller',
14
+ scope: 'Bidirectional sync — event normalization, resource upsert, watermark, ownership, tombstones',
15
+ owns: ['event normalization', 'resource upsert', 'watermark tracking', 'ownership modes', 'tombstones'],
16
+ delegatesTo: ['resource-model'],
17
+ mustNotOwn: ['HMAC verification', 'webhook delivery tracking']
18
+ };
19
+
20
+ /**
21
+ * Create a sync controller that manages bidirectional sync between external
22
+ * providers and the Kradle resource store.
23
+ *
24
+ * @param {{ persistFn?: (resource: object) => Promise<any> }} [opts]
25
+ * Optional persistFn is called (fire-and-forget) after watermark/resource changes.
26
+ * It receives a K8s-style CRD resource representing the changed state.
27
+ * @returns {object}
28
+ */
29
+ export function createSyncController({ persistFn } = {}) {
30
+ /** @type {Map<string, string>} bindingRef → ISO timestamp watermark */
31
+ const watermarks = new Map();
32
+
33
+ /** @type {Map<string, object>} `${namespace}/${kind}/${localName}` → resource */
34
+ const resources = new Map();
35
+
36
+ /** @type {Map<string, object>} nativeId → tombstone record */
37
+ const tombstones = new Map();
38
+
39
+ /**
40
+ * Fire-and-forget persistence: call persistFn without blocking the caller.
41
+ * @param {object} resource
42
+ */
43
+ function persist(resource) {
44
+ if (typeof persistFn === 'function') {
45
+ // Intentionally not awaited — persistence is async and non-blocking
46
+ Promise.resolve(persistFn(resource)).catch(() => {
47
+ // Swallow errors in fire-and-forget path; caller may wire monitoring separately
48
+ });
49
+ }
50
+ }
51
+
52
+ return {
53
+ /**
54
+ * Normalize a raw provider event into a canonical internal format.
55
+ *
56
+ * @param {{ eventType: string, action?: string, nativeId: string, providerRef: string,
57
+ * resourceKind: string, data?: object, receivedAt?: string }} rawEvent
58
+ * @returns {{ eventType: string, action: string, nativeId: string, providerRef: string,
59
+ * resourceKind: string, data: object, receivedAt: string, canonicalAt: string }}
60
+ */
61
+ normalizeEvent(rawEvent) {
62
+ return {
63
+ eventType: rawEvent.eventType,
64
+ action: rawEvent.action || 'unknown',
65
+ nativeId: rawEvent.nativeId,
66
+ providerRef: rawEvent.providerRef,
67
+ resourceKind: rawEvent.resourceKind,
68
+ data: rawEvent.data || {},
69
+ receivedAt: rawEvent.receivedAt || new Date().toISOString(),
70
+ canonicalAt: new Date().toISOString()
71
+ };
72
+ },
73
+
74
+ /**
75
+ * Upsert a resource in the local store with an external identity envelope.
76
+ *
77
+ * If the resource already exists (matched by localName + namespace + kind), it will
78
+ * be updated while preserving the nativeId and firstSyncedAt from the first sync.
79
+ *
80
+ * @param {{ kind: string, localName: string, namespace?: string, spec: object,
81
+ * externalEnvelope: { nativeId: string, url: string, etag: string, providerRef: string } }} params
82
+ * @returns {object} The created/updated resource
83
+ */
84
+ upsertResource({ kind, localName, namespace = 'default', spec, externalEnvelope }) {
85
+ const key = `${namespace}/${kind}/${localName}`;
86
+ const existing = resources.get(key);
87
+ const now = new Date().toISOString();
88
+
89
+ const resource = {
90
+ apiVersion: 'kradle.a5c.ai/v1alpha1',
91
+ kind,
92
+ metadata: {
93
+ name: localName,
94
+ namespace,
95
+ labels: {},
96
+ annotations: {}
97
+ },
98
+ spec: { ...spec },
99
+ status: {
100
+ phase: 'Synced',
101
+ external: {
102
+ nativeId: externalEnvelope.nativeId,
103
+ url: externalEnvelope.url,
104
+ etag: externalEnvelope.etag,
105
+ providerRef: externalEnvelope.providerRef,
106
+ lastSyncedAt: now,
107
+ firstSyncedAt: existing
108
+ ? existing.status.external.firstSyncedAt
109
+ : now
110
+ }
111
+ }
112
+ };
113
+
114
+ resources.set(key, resource);
115
+ persist(resource);
116
+ return resource;
117
+ },
118
+
119
+ /**
120
+ * Advance the high-watermark timestamp for a given binding.
121
+ * If the new timestamp is not later than the existing one, it is ignored.
122
+ *
123
+ * @param {string} bindingRef
124
+ * @param {string} timestamp ISO 8601 timestamp
125
+ */
126
+ updateWatermark(bindingRef, timestamp) {
127
+ const current = watermarks.get(bindingRef);
128
+ if (!current || timestamp > current) {
129
+ watermarks.set(bindingRef, timestamp);
130
+ // Persist watermark as a CRD-shaped resource
131
+ persist({
132
+ apiVersion: 'kradle.a5c.ai/v1alpha1',
133
+ kind: 'ExternalSyncWatermark',
134
+ metadata: {
135
+ name: `watermark-${bindingRef.replace(/[^a-zA-Z0-9]/g, '-')}`,
136
+ namespace: 'default',
137
+ labels: {},
138
+ annotations: {}
139
+ },
140
+ spec: {
141
+ bindingRef,
142
+ watermark: timestamp
143
+ },
144
+ status: {
145
+ lastUpdatedAt: new Date().toISOString()
146
+ }
147
+ });
148
+ }
149
+ },
150
+
151
+ /**
152
+ * Retrieve the current high-watermark for a given binding.
153
+ *
154
+ * @param {string} bindingRef
155
+ * @returns {string|null}
156
+ */
157
+ getWatermark(bindingRef) {
158
+ return watermarks.has(bindingRef) ? watermarks.get(bindingRef) : null;
159
+ },
160
+
161
+ /**
162
+ * Apply an ownership mode policy to determine whether an operation is allowed.
163
+ *
164
+ * Modes:
165
+ * - 'bidirectional' — both kradle and external provider may write
166
+ * - 'external-owned' — only the external provider may write; kradle is read-only
167
+ * - 'kradle-owned' — only kradle may write; external provider is read-only
168
+ *
169
+ * @param {{ ownershipMode: string, operation: string, origin: string }} params
170
+ * @returns {{ allowed: boolean, reason: string|null }}
171
+ */
172
+ applyOwnershipMode({ ownershipMode, operation, origin }) {
173
+ if (ownershipMode === 'bidirectional') {
174
+ return { allowed: true, reason: null };
175
+ }
176
+
177
+ if (ownershipMode === 'external-owned') {
178
+ if (origin === 'kradle' && operation === 'write') {
179
+ return {
180
+ allowed: false,
181
+ reason: 'external-owned mode — kradle writes are blocked; this resource is read-only from kradle perspective'
182
+ };
183
+ }
184
+ return { allowed: true, reason: null };
185
+ }
186
+
187
+ if (ownershipMode === 'kradle-owned') {
188
+ if (origin === 'external' && operation === 'write') {
189
+ return {
190
+ allowed: false,
191
+ reason: 'kradle-owned mode — external provider writes are blocked; kradle is the authoritative source'
192
+ };
193
+ }
194
+ return { allowed: true, reason: null };
195
+ }
196
+
197
+ // Unknown mode — deny by default
198
+ return {
199
+ allowed: false,
200
+ reason: `unknown ownership mode: ${ownershipMode}`
201
+ };
202
+ },
203
+
204
+ /**
205
+ * Create a tombstone record marking that an external resource has been deleted.
206
+ *
207
+ * @param {{ nativeId: string, providerRef: string, resourceKind: string,
208
+ * localRef: string, deletedAt?: string }} params
209
+ * @returns {{ nativeId: string, providerRef: string, resourceKind: string,
210
+ * localRef: string, tombstoned: true, deletedAt: string }}
211
+ */
212
+ createTombstone({ nativeId, providerRef, resourceKind, localRef, deletedAt }) {
213
+ const record = {
214
+ nativeId,
215
+ providerRef,
216
+ resourceKind,
217
+ localRef,
218
+ tombstoned: true,
219
+ deletedAt: deletedAt || new Date().toISOString()
220
+ };
221
+ tombstones.set(nativeId, record);
222
+ return record;
223
+ },
224
+
225
+ /**
226
+ * Retrieve a tombstone record by nativeId, or null if not found.
227
+ *
228
+ * @param {string} nativeId
229
+ * @returns {object|null}
230
+ */
231
+ getTombstone(nativeId) {
232
+ return tombstones.has(nativeId) ? tombstones.get(nativeId) : null;
233
+ }
234
+ };
235
+ }