agl 1.0.17 → 21.0.2

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 (277) hide show
  1. package/.smartconfig.json +55 -0
  2. package/changelog.md +1026 -0
  3. package/cli.js +14 -0
  4. package/dist_serve/bundle.js +44778 -0
  5. package/dist_serve/index.html +13 -0
  6. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  7. package/dist_ts/00_commitinfo_data.js +9 -0
  8. package/dist_ts/classes.authmodels.d.ts +126 -0
  9. package/dist_ts/classes.authmodels.js +1422 -0
  10. package/dist_ts/classes.authstore.d.ts +160 -0
  11. package/dist_ts/classes.authstore.js +2483 -0
  12. package/dist_ts/classes.browserresourcehost.d.ts +85 -0
  13. package/dist_ts/classes.browserresourcehost.js +912 -0
  14. package/dist_ts/classes.cli.d.ts +21 -0
  15. package/dist_ts/classes.cli.js +1082 -0
  16. package/dist_ts/classes.config.d.ts +23 -0
  17. package/dist_ts/classes.config.js +267 -0
  18. package/dist_ts/classes.controller.d.ts +523 -0
  19. package/dist_ts/classes.controller.js +10611 -0
  20. package/dist_ts/classes.controllermanagement.d.ts +32 -0
  21. package/dist_ts/classes.controllermanagement.js +222 -0
  22. package/dist_ts/classes.draftmanager.d.ts +30 -0
  23. package/dist_ts/classes.draftmanager.js +179 -0
  24. package/dist_ts/classes.embeddeddb.d.ts +30 -0
  25. package/dist_ts/classes.embeddeddb.js +159 -0
  26. package/dist_ts/classes.flexframedtransport.d.ts +56 -0
  27. package/dist_ts/classes.flexframedtransport.js +275 -0
  28. package/dist_ts/classes.flexmodels.private.d.ts +231 -0
  29. package/dist_ts/classes.flexmodels.private.js +985 -0
  30. package/dist_ts/classes.flexmodels.public.d.ts +207 -0
  31. package/dist_ts/classes.flexmodels.public.js +885 -0
  32. package/dist_ts/classes.flexprojectionreader.d.ts +89 -0
  33. package/dist_ts/classes.flexprojectionreader.js +580 -0
  34. package/dist_ts/classes.flexservice.d.ts +196 -0
  35. package/dist_ts/classes.flexservice.js +3704 -0
  36. package/dist_ts/classes.flexstore.d.ts +174 -0
  37. package/dist_ts/classes.flexstore.js +3400 -0
  38. package/dist_ts/classes.flexsupervisor.d.ts +105 -0
  39. package/dist_ts/classes.flexsupervisor.js +1191 -0
  40. package/dist_ts/classes.gitreversion.d.ts +431 -0
  41. package/dist_ts/classes.gitreversion.js +10592 -0
  42. package/dist_ts/classes.harnessoperationmanager.d.ts +113 -0
  43. package/dist_ts/classes.harnessoperationmanager.js +602 -0
  44. package/dist_ts/classes.opencodeclient.d.ts +146 -0
  45. package/dist_ts/classes.opencodeclient.js +3003 -0
  46. package/dist_ts/classes.opencodesupervisor.d.ts +60 -0
  47. package/dist_ts/classes.opencodesupervisor.js +1003 -0
  48. package/dist_ts/classes.passkeymanager.d.ts +34 -0
  49. package/dist_ts/classes.passkeymanager.js +456 -0
  50. package/dist_ts/classes.processinspection.d.ts +50 -0
  51. package/dist_ts/classes.processinspection.js +484 -0
  52. package/dist_ts/classes.resourcecoordinator.d.ts +64 -0
  53. package/dist_ts/classes.resourcecoordinator.js +294 -0
  54. package/dist_ts/classes.sessionintelligencereader.d.ts +13 -0
  55. package/dist_ts/classes.sessionintelligencereader.js +40 -0
  56. package/dist_ts/classes.systemmetricssampler.d.ts +53 -0
  57. package/dist_ts/classes.systemmetricssampler.js +168 -0
  58. package/dist_ts/classes.terminalmanager.d.ts +95 -0
  59. package/dist_ts/classes.terminalmanager.js +981 -0
  60. package/dist_ts/classes.terminalresourcehost.d.ts +20 -0
  61. package/dist_ts/classes.terminalresourcehost.js +84 -0
  62. package/dist_ts/classes.upgradecoordinator.d.ts +244 -0
  63. package/dist_ts/classes.upgradecoordinator.js +2155 -0
  64. package/dist_ts/classes.upgradetransaction.d.ts +92 -0
  65. package/dist_ts/classes.upgradetransaction.js +2125 -0
  66. package/dist_ts/classes.uploadmanager.d.ts +24 -0
  67. package/dist_ts/classes.uploadmanager.js +210 -0
  68. package/dist_ts/classes.validation.d.ts +91 -0
  69. package/dist_ts/classes.validation.js +1087 -0
  70. package/dist_ts/flexharness.child.d.ts +1 -0
  71. package/dist_ts/flexharness.child.js +28 -0
  72. package/dist_ts/functions.canonicaldirectory.d.ts +21 -0
  73. package/dist_ts/functions.canonicaldirectory.js +144 -0
  74. package/dist_ts/functions.controllerdataroot.d.ts +15 -0
  75. package/dist_ts/functions.controllerdataroot.js +76 -0
  76. package/dist_ts/functions.embeddeddb.d.ts +2 -0
  77. package/dist_ts/functions.embeddeddb.js +21 -0
  78. package/dist_ts/functions.flexcredentialstore.d.ts +16 -0
  79. package/dist_ts/functions.flexcredentialstore.js +93 -0
  80. package/dist_ts/functions.opencodeauth.d.ts +3 -0
  81. package/dist_ts/functions.opencodeauth.js +31 -0
  82. package/dist_ts/functions.runtimeenvironment.d.ts +2 -0
  83. package/dist_ts/functions.runtimeenvironment.js +79 -0
  84. package/dist_ts/functions.upgradefollower.d.ts +3 -0
  85. package/dist_ts/functions.upgradefollower.js +95 -0
  86. package/dist_ts/index.d.ts +24 -0
  87. package/dist_ts/index.js +25 -0
  88. package/dist_ts/interfaces.auth.d.ts +181 -0
  89. package/dist_ts/interfaces.auth.js +82 -0
  90. package/dist_ts/interfaces.config.d.ts +34 -0
  91. package/dist_ts/interfaces.config.js +2 -0
  92. package/dist_ts/interfaces.flexipc.d.ts +939 -0
  93. package/dist_ts/interfaces.flexipc.js +1492 -0
  94. package/dist_ts/interfaces.gitreversion.d.ts +246 -0
  95. package/dist_ts/interfaces.gitreversion.js +2 -0
  96. package/dist_ts/interfaces.lifecycle.d.ts +37 -0
  97. package/dist_ts/interfaces.lifecycle.js +2 -0
  98. package/dist_ts/interfaces.opencode.d.ts +101 -0
  99. package/dist_ts/interfaces.opencode.js +3 -0
  100. package/dist_ts/interfaces.projects.d.ts +156 -0
  101. package/dist_ts/interfaces.projects.js +5 -0
  102. package/dist_ts/plugins.d.ts +36 -0
  103. package/dist_ts/plugins.js +40 -0
  104. package/dist_ts_interfaces/index.d.ts +2 -0
  105. package/dist_ts_interfaces/index.js +3 -0
  106. package/dist_ts_interfaces/interfaces.d.ts +747 -0
  107. package/dist_ts_interfaces/interfaces.js +47 -0
  108. package/dist_ts_interfaces/plugins.d.ts +4 -0
  109. package/dist_ts_interfaces/plugins.js +3 -0
  110. package/dist_ts_interfaces/requests.d.ts +806 -0
  111. package/dist_ts_interfaces/requests.js +2 -0
  112. package/dist_ts_migration/classes.documentmigrationrunner.d.ts +8 -0
  113. package/dist_ts_migration/classes.documentmigrationrunner.js +24 -0
  114. package/dist_ts_migration/classes.flexmigrationrunner.d.ts +11 -0
  115. package/dist_ts_migration/classes.flexmigrationrunner.js +25 -0
  116. package/dist_ts_migration/classes.flexpostharnessmigrationrunner.d.ts +10 -0
  117. package/dist_ts_migration/classes.flexpostharnessmigrationrunner.js +15 -0
  118. package/dist_ts_migration/classes.migrationrunner.d.ts +8 -0
  119. package/dist_ts_migration/classes.migrationrunner.js +34 -0
  120. package/dist_ts_migration/index.d.ts +12 -0
  121. package/dist_ts_migration/index.js +13 -0
  122. package/dist_ts_migration/v10_flexharnessv3migration.d.ts +11 -0
  123. package/dist_ts_migration/v10_flexharnessv3migration.js +23 -0
  124. package/dist_ts_migration/v13_flexprojectionorder.d.ts +11 -0
  125. package/dist_ts_migration/v13_flexprojectionorder.js +23 -0
  126. package/dist_ts_migration/v16_flexprovidercredentials.d.ts +22 -0
  127. package/dist_ts_migration/v16_flexprovidercredentials.js +149 -0
  128. package/dist_ts_migration/v18_resourcelayout.d.ts +22 -0
  129. package/dist_ts_migration/v18_resourcelayout.js +23 -0
  130. package/dist_ts_migration/v18_resourcelayoutmigration.d.ts +6 -0
  131. package/dist_ts_migration/v18_resourcelayoutmigration.js +61 -0
  132. package/dist_ts_migration/v19_flexprojectmanagement.d.ts +11 -0
  133. package/dist_ts_migration/v19_flexprojectmanagement.js +21 -0
  134. package/dist_ts_migration/v1_embeddeddatabaselocation.d.ts +28 -0
  135. package/dist_ts_migration/v1_embeddeddatabaselocation.js +415 -0
  136. package/dist_ts_migration/v22_projectfilesystemidentitymigration.d.ts +7 -0
  137. package/dist_ts_migration/v22_projectfilesystemidentitymigration.js +105 -0
  138. package/dist_ts_migration/v2_controllerdataroot.d.ts +130 -0
  139. package/dist_ts_migration/v2_controllerdataroot.js +1887 -0
  140. package/dist_ts_migration/v6_protocoldocumentmigration.d.ts +7 -0
  141. package/dist_ts_migration/v6_protocoldocumentmigration.js +100 -0
  142. package/dist_ts_migration/v6_protocoldocuments.d.ts +7 -0
  143. package/dist_ts_migration/v6_protocoldocuments.js +138 -0
  144. package/html/index.html +13 -0
  145. package/{LICENSE → license.md} +2 -2
  146. package/package.json +86 -48
  147. package/readme.md +480 -0
  148. package/readme.plan.md +188 -0
  149. package/ts/00_commitinfo_data.ts +8 -0
  150. package/ts/classes.authmodels.ts +1245 -0
  151. package/ts/classes.authstore.ts +3274 -0
  152. package/ts/classes.browserresourcehost.ts +1205 -0
  153. package/ts/classes.cli.ts +1254 -0
  154. package/ts/classes.config.ts +351 -0
  155. package/ts/classes.controller.ts +14426 -0
  156. package/ts/classes.controllermanagement.ts +330 -0
  157. package/ts/classes.draftmanager.ts +232 -0
  158. package/ts/classes.embeddeddb.ts +166 -0
  159. package/ts/classes.flexframedtransport.ts +283 -0
  160. package/ts/classes.flexmodels.private.ts +823 -0
  161. package/ts/classes.flexmodels.public.ts +732 -0
  162. package/ts/classes.flexprojectionreader.ts +816 -0
  163. package/ts/classes.flexservice.ts +4302 -0
  164. package/ts/classes.flexstore.ts +4565 -0
  165. package/ts/classes.flexsupervisor.ts +1453 -0
  166. package/ts/classes.gitreversion.ts +13353 -0
  167. package/ts/classes.harnessoperationmanager.ts +870 -0
  168. package/ts/classes.opencodeclient.ts +4110 -0
  169. package/ts/classes.opencodesupervisor.ts +1280 -0
  170. package/ts/classes.passkeymanager.ts +570 -0
  171. package/ts/classes.processinspection.ts +623 -0
  172. package/ts/classes.resourcecoordinator.ts +436 -0
  173. package/ts/classes.sessionintelligencereader.ts +52 -0
  174. package/ts/classes.systemmetricssampler.ts +215 -0
  175. package/ts/classes.terminalmanager.ts +1207 -0
  176. package/ts/classes.terminalresourcehost.ts +122 -0
  177. package/ts/classes.upgradecoordinator.ts +2675 -0
  178. package/ts/classes.upgradetransaction.ts +2900 -0
  179. package/ts/classes.uploadmanager.ts +240 -0
  180. package/ts/classes.validation.ts +1409 -0
  181. package/ts/flexharness.child.ts +35 -0
  182. package/ts/functions.canonicaldirectory.ts +213 -0
  183. package/ts/functions.controllerdataroot.ts +107 -0
  184. package/ts/functions.embeddeddb.ts +22 -0
  185. package/ts/functions.flexcredentialstore.ts +128 -0
  186. package/ts/functions.opencodeauth.ts +34 -0
  187. package/ts/functions.runtimeenvironment.ts +90 -0
  188. package/ts/functions.upgradefollower.ts +115 -0
  189. package/ts/index.ts +36 -6
  190. package/ts/interfaces.auth.ts +316 -0
  191. package/ts/interfaces.config.ts +37 -0
  192. package/ts/interfaces.flexipc.ts +2586 -0
  193. package/ts/interfaces.gitreversion.ts +336 -0
  194. package/ts/interfaces.lifecycle.ts +46 -0
  195. package/ts/interfaces.opencode.ts +153 -0
  196. package/ts/interfaces.projects.ts +182 -0
  197. package/ts/plugins.ts +64 -0
  198. package/ts_interfaces/index.ts +2 -0
  199. package/ts_interfaces/interfaces.ts +827 -0
  200. package/ts_interfaces/plugins.ts +15 -0
  201. package/ts_interfaces/requests.ts +1186 -0
  202. package/ts_migration/classes.documentmigrationrunner.ts +32 -0
  203. package/ts_migration/classes.flexmigrationrunner.ts +33 -0
  204. package/ts_migration/classes.flexpostharnessmigrationrunner.ts +22 -0
  205. package/ts_migration/classes.migrationrunner.ts +40 -0
  206. package/ts_migration/index.ts +12 -0
  207. package/ts_migration/v10_flexharnessv3migration.ts +35 -0
  208. package/ts_migration/v13_flexprojectionorder.ts +33 -0
  209. package/ts_migration/v16_flexprovidercredentials.ts +196 -0
  210. package/ts_migration/v18_resourcelayout.ts +55 -0
  211. package/ts_migration/v18_resourcelayoutmigration.ts +68 -0
  212. package/ts_migration/v19_flexprojectmanagement.ts +28 -0
  213. package/ts_migration/v1_embeddeddatabaselocation.ts +560 -0
  214. package/ts_migration/v22_projectfilesystemidentitymigration.ts +121 -0
  215. package/ts_migration/v2_controllerdataroot.ts +2349 -0
  216. package/ts_migration/v6_protocoldocumentmigration.ts +144 -0
  217. package/ts_migration/v6_protocoldocuments.ts +198 -0
  218. package/ts_web/00_commitinfo_data.ts +8 -0
  219. package/ts_web/classes.controllersocketclient.ts +517 -0
  220. package/ts_web/classes.sessiondraftsync.ts +390 -0
  221. package/ts_web/elements.harnesscontrollerapp.ts +11494 -0
  222. package/ts_web/index.ts +2 -0
  223. package/ts_web/plugins.ts +31 -0
  224. package/.gitlab-ci.yml +0 -71
  225. package/.npmignore +0 -5
  226. package/README.md +0 -40
  227. package/dist/cli.js +0 -2
  228. package/dist/index.d.ts +0 -0
  229. package/dist/index.js +0 -9
  230. package/dist/mod_bundle/index.d.ts +0 -1
  231. package/dist/mod_bundle/index.js +0 -18
  232. package/dist/mod_bundle/mod.plugins.d.ts +0 -4
  233. package/dist/mod_bundle/mod.plugins.js +0 -11
  234. package/dist/mod_install/index.d.ts +0 -4
  235. package/dist/mod_install/index.js +0 -21
  236. package/dist/mod_install/mod.plugins.d.ts +0 -1
  237. package/dist/mod_install/mod.plugins.js +0 -7
  238. package/dist/mod_standard/index.d.ts +0 -4
  239. package/dist/mod_standard/index.js +0 -21
  240. package/dist/mod_standard/mod.browser.d.ts +0 -6
  241. package/dist/mod_standard/mod.browser.js +0 -14
  242. package/dist/mod_standard/mod.clean.d.ts +0 -4
  243. package/dist/mod_standard/mod.clean.js +0 -12
  244. package/dist/mod_standard/mod.compile.d.ts +0 -4
  245. package/dist/mod_standard/mod.compile.js +0 -21
  246. package/dist/mod_standard/mod.plugins.d.ts +0 -8
  247. package/dist/mod_standard/mod.plugins.js +0 -19
  248. package/dist/mod_standard/mod.test.d.ts +0 -4
  249. package/dist/mod_standard/mod.test.js +0 -12
  250. package/dist/plr.cli.d.ts +0 -3
  251. package/dist/plr.cli.js +0 -57
  252. package/dist/plr.config.d.ts +0 -11
  253. package/dist/plr.config.js +0 -26
  254. package/dist/plr.mods.d.ts +0 -17
  255. package/dist/plr.mods.js +0 -17
  256. package/dist/plr.paths.d.ts +0 -19
  257. package/dist/plr.paths.js +0 -26
  258. package/dist/plr.plugins.d.ts +0 -8
  259. package/dist/plr.plugins.js +0 -16
  260. package/npmextra.json +0 -10
  261. package/ts/mod_bundle/index.ts +0 -20
  262. package/ts/mod_bundle/mod.plugins.ts +0 -9
  263. package/ts/mod_install/index.ts +0 -20
  264. package/ts/mod_install/mod.plugins.ts +0 -1
  265. package/ts/mod_standard/index.ts +0 -23
  266. package/ts/mod_standard/mod.browser.ts +0 -17
  267. package/ts/mod_standard/mod.clean.ts +0 -13
  268. package/ts/mod_standard/mod.compile.ts +0 -23
  269. package/ts/mod_standard/mod.plugins.ts +0 -17
  270. package/ts/mod_standard/mod.test.ts +0 -13
  271. package/ts/plr.cli.ts +0 -60
  272. package/ts/plr.config.ts +0 -36
  273. package/ts/plr.mods.ts +0 -25
  274. package/ts/plr.paths.ts +0 -29
  275. package/ts/plr.plugins.ts +0 -17
  276. package/tslint.json +0 -3
  277. package/yarn.lock +0 -3464
@@ -0,0 +1,1245 @@
1
+ import * as plugins from './plugins.js';
2
+ import {
3
+ controllerAuditEventTypes,
4
+ type IControllerAuditEvent,
5
+ type IControllerAuthDocument,
6
+ type IControllerTempPasswordDocument,
7
+ type ISetupAuthorityState,
8
+ type IStoredPasskeyCredential,
9
+ type IWebAuthnCeremonyDocument,
10
+ } from './interfaces.auth.js';
11
+ import type { IControllerRuntimeConfig } from './interfaces.config.js';
12
+ import {
13
+ controllerFilesystemIdentityMaximumComponents,
14
+ type IControllerFilesystemIdentity,
15
+ type IControllerProjectDocument,
16
+ type IControllerResourceDocument,
17
+ type IControllerSessionStateDocument,
18
+ type IControllerSessionGroupsDocument,
19
+ type IControllerSettingsDocument,
20
+ } from './interfaces.projects.js';
21
+ import {
22
+ controllerRuntimeIdKey,
23
+ type TControllerModelChoice,
24
+ type IControllerRuntimeId,
25
+ type IControllerSessionGroup,
26
+ type TControllerSessionId,
27
+ type TControllerHarnessId,
28
+ } from '../ts_interfaces/index.js';
29
+
30
+ const base64UrlPattern = /^[A-Za-z0-9_-]+$/;
31
+ const credentialTransports = new Set([
32
+ 'ble',
33
+ 'cable',
34
+ 'hybrid',
35
+ 'internal',
36
+ 'nfc',
37
+ 'smart-card',
38
+ 'usb',
39
+ ]);
40
+ const auditEventTypes: ReadonlySet<string> = new Set(controllerAuditEventTypes);
41
+ const auditOutcomes = new Set(['attempted', 'succeeded', 'denied', 'failed']);
42
+ const auditRetentionSeconds = 90 * 24 * 60 * 60;
43
+
44
+ const isPlainObject = (valueArg: unknown): valueArg is Record<string, unknown> =>
45
+ typeof valueArg === 'object'
46
+ && valueArg !== null
47
+ && !Array.isArray(valueArg)
48
+ && (
49
+ Object.getPrototypeOf(valueArg) === Object.prototype
50
+ || Object.getPrototypeOf(valueArg) === null
51
+ );
52
+
53
+ const hasExactKeys = (
54
+ valueArg: Record<string, unknown>,
55
+ requiredKeysArg: readonly string[],
56
+ optionalKeysArg: readonly string[] = [],
57
+ ): boolean => {
58
+ const keys = Object.keys(valueArg);
59
+ return requiredKeysArg.every((key) => Object.hasOwn(valueArg, key))
60
+ && keys.every((key) => requiredKeysArg.includes(key) || optionalKeysArg.includes(key));
61
+ };
62
+
63
+ const isNonEmptyString = (valueArg: unknown): valueArg is string =>
64
+ typeof valueArg === 'string' && valueArg.length > 0;
65
+
66
+ const isRuntimeId = (
67
+ valueArg: unknown,
68
+ allowedHarnessIdsArg: readonly TControllerHarnessId[],
69
+ ): valueArg is IControllerRuntimeId =>
70
+ isPlainObject(valueArg)
71
+ && hasExactKeys(valueArg, ['harnessId', 'nativeId'])
72
+ && allowedHarnessIdsArg.includes(valueArg.harnessId as TControllerHarnessId)
73
+ && isNonEmptyString(valueArg.nativeId)
74
+ && Buffer.byteLength(valueArg.nativeId, 'utf8') <= 512;
75
+
76
+ const isBase64Url = (valueArg: unknown, byteLengthArg?: number): valueArg is string => {
77
+ if (!isNonEmptyString(valueArg) || !base64UrlPattern.test(valueArg)) return false;
78
+ if (byteLengthArg === undefined) return true;
79
+ try {
80
+ return Buffer.from(valueArg, 'base64url').byteLength === byteLengthArg;
81
+ } catch {
82
+ return false;
83
+ }
84
+ };
85
+
86
+ const isValidDate = (valueArg: unknown): valueArg is Date =>
87
+ valueArg instanceof Date && Number.isFinite(valueArg.getTime());
88
+
89
+ const isValidPort = (valueArg: unknown): valueArg is number =>
90
+ Number.isSafeInteger(valueArg) && (valueArg as number) >= 1 && (valueArg as number) <= 65535;
91
+
92
+ const isRuntimeConfig = (valueArg: unknown): valueArg is IControllerRuntimeConfig => {
93
+ if (!isPlainObject(valueArg)) return false;
94
+ if (
95
+ // projectsRoot is optional only because pre-v2 documents lack it; once
96
+ // present it must be a canonical absolute path like the workspace.
97
+ !hasExactKeys(valueArg, [
98
+ 'controllerPort',
99
+ 'publicOrigin',
100
+ 'rpId',
101
+ 'workspaceDirectory',
102
+ 'opencodePort',
103
+ 'tlsMode',
104
+ ], ['projectsRoot'])
105
+ || (
106
+ Object.hasOwn(valueArg, 'projectsRoot')
107
+ && (
108
+ !isNonEmptyString(valueArg.projectsRoot)
109
+ || !plugins.path.isAbsolute(valueArg.projectsRoot)
110
+ )
111
+ )
112
+ || !isValidPort(valueArg.controllerPort)
113
+ || !isValidPort(valueArg.opencodePort)
114
+ || valueArg.controllerPort === valueArg.opencodePort
115
+ || !isNonEmptyString(valueArg.publicOrigin)
116
+ || !isNonEmptyString(valueArg.rpId)
117
+ || !isNonEmptyString(valueArg.workspaceDirectory)
118
+ || !plugins.path.isAbsolute(valueArg.workspaceDirectory)
119
+ || !['local', 'proxy'].includes(valueArg.tlsMode as string)
120
+ ) {
121
+ return false;
122
+ }
123
+
124
+ try {
125
+ const origin = new URL(valueArg.publicOrigin);
126
+ const hostname = origin.hostname.toLowerCase().replace(/\.$/, '');
127
+ const rpId = valueArg.rpId.toLowerCase().replace(/\.$/, '');
128
+ if (
129
+ origin.origin !== valueArg.publicOrigin
130
+ || (origin.protocol !== 'http:' && origin.protocol !== 'https:')
131
+ || origin.username.length > 0
132
+ || origin.password.length > 0
133
+ || origin.pathname !== '/'
134
+ || origin.search.length > 0
135
+ || origin.hash.length > 0
136
+ || valueArg.rpId !== rpId
137
+ || rpId.length === 0
138
+ || rpId.includes('/')
139
+ || rpId.includes(':')
140
+ || /\s/.test(rpId)
141
+ || (hostname !== rpId && !hostname.endsWith(`.${rpId}`))
142
+ ) {
143
+ return false;
144
+ }
145
+ if (valueArg.tlsMode === 'local') {
146
+ return origin.protocol === 'http:' && hostname === 'localhost';
147
+ }
148
+ return origin.protocol === 'https:';
149
+ } catch {
150
+ return false;
151
+ }
152
+ };
153
+
154
+ const isSetupAuthorityState = (valueArg: unknown): valueArg is ISetupAuthorityState => {
155
+ if (!isPlainObject(valueArg)) return false;
156
+ return hasExactKeys(valueArg, ['generation', 'hash', 'expiresAt'])
157
+ && isBase64Url(valueArg.generation, 32)
158
+ && isBase64Url(valueArg.hash, 32)
159
+ && isValidDate(valueArg.expiresAt);
160
+ };
161
+
162
+ const isStoredCredential = (valueArg: unknown): valueArg is IStoredPasskeyCredential => {
163
+ if (!isPlainObject(valueArg)) return false;
164
+ return hasExactKeys(valueArg, [
165
+ 'id',
166
+ 'publicKey',
167
+ 'counter',
168
+ 'transports',
169
+ 'deviceType',
170
+ 'backedUp',
171
+ 'createdAt',
172
+ 'counterUpdateId',
173
+ ])
174
+ && isBase64Url(valueArg.id)
175
+ && isBase64Url(valueArg.publicKey)
176
+ && Number.isSafeInteger(valueArg.counter)
177
+ && (valueArg.counter as number) >= 0
178
+ && Array.isArray(valueArg.transports)
179
+ && valueArg.transports.every((entry) => credentialTransports.has(entry as string))
180
+ && (valueArg.deviceType === 'singleDevice' || valueArg.deviceType === 'multiDevice')
181
+ && typeof valueArg.backedUp === 'boolean'
182
+ && isValidDate(valueArg.createdAt)
183
+ && isBase64Url(valueArg.counterUpdateId, 32);
184
+ };
185
+
186
+ export const assertControllerAuthDocument = (
187
+ valueArg: unknown,
188
+ ): asserts valueArg is IControllerAuthDocument => {
189
+ if (!isPlainObject(valueArg)) throw new Error('Invalid controller authentication document.');
190
+ if (
191
+ !hasExactKeys(valueArg, [
192
+ 'id',
193
+ 'config',
194
+ 'authState',
195
+ 'setup',
196
+ 'webauthnUserId',
197
+ 'credentials',
198
+ ])
199
+ || !isRuntimeConfig(valueArg.config)
200
+ || valueArg.id !== `controller:${valueArg.config.controllerPort}`
201
+ || !isBase64Url(valueArg.webauthnUserId, 32)
202
+ || !Array.isArray(valueArg.credentials)
203
+ || !valueArg.credentials.every(isStoredCredential)
204
+ || new Set(valueArg.credentials.map((credential) => credential.id)).size
205
+ !== valueArg.credentials.length
206
+ ) {
207
+ throw new Error('Invalid controller authentication document.');
208
+ }
209
+
210
+ if (valueArg.credentials.length === 0) {
211
+ if (
212
+ valueArg.authState !== 'setupRequired'
213
+ || (valueArg.setup !== null && !isSetupAuthorityState(valueArg.setup))
214
+ ) {
215
+ throw new Error('Invalid controller setup state.');
216
+ }
217
+ return;
218
+ }
219
+
220
+ if (valueArg.authState !== 'ready' || valueArg.setup !== null) {
221
+ throw new Error('Invalid enrolled controller authentication state.');
222
+ }
223
+ };
224
+
225
+ const assertCeremonyStateFields = (valueArg: Record<string, unknown>): boolean => {
226
+ if (valueArg.state === 'pending') {
227
+ return !Object.hasOwn(valueArg, 'consumedAt') && !Object.hasOwn(valueArg, 'consumedNonce');
228
+ }
229
+ return valueArg.state === 'consumed'
230
+ && isValidDate(valueArg.consumedAt)
231
+ && isBase64Url(valueArg.consumedNonce, 32);
232
+ };
233
+
234
+ export const assertWebAuthnCeremonyDocument = (
235
+ valueArg: unknown,
236
+ ): asserts valueArg is IWebAuthnCeremonyDocument => {
237
+ if (!isPlainObject(valueArg)) throw new Error('Invalid WebAuthn ceremony document.');
238
+ const optionalKeys = valueArg.kind === 'setup'
239
+ ? ['consumedAt', 'consumedNonce']
240
+ : ['consumedAt', 'consumedNonce'];
241
+ const requiredKeys = [
242
+ 'id',
243
+ 'kind',
244
+ 'state',
245
+ 'peerId',
246
+ 'challenge',
247
+ 'origin',
248
+ 'rpId',
249
+ 'userId',
250
+ 'createdAt',
251
+ 'expiresAt',
252
+ ...(valueArg.kind === 'setup' ? ['setupGeneration', 'setupHash'] : []),
253
+ ];
254
+ if (
255
+ !hasExactKeys(valueArg, requiredKeys, optionalKeys)
256
+ || !isBase64Url(valueArg.id, 32)
257
+ || (valueArg.kind !== 'setup' && valueArg.kind !== 'authentication')
258
+ || !isNonEmptyString(valueArg.peerId)
259
+ || valueArg.peerId.length > 512
260
+ || !isBase64Url(valueArg.challenge)
261
+ || !isNonEmptyString(valueArg.origin)
262
+ || !isNonEmptyString(valueArg.rpId)
263
+ || !isBase64Url(valueArg.userId, 32)
264
+ || !isValidDate(valueArg.createdAt)
265
+ || !isValidDate(valueArg.expiresAt)
266
+ || valueArg.expiresAt.getTime() <= valueArg.createdAt.getTime()
267
+ || !assertCeremonyStateFields(valueArg)
268
+ ) {
269
+ throw new Error('Invalid WebAuthn ceremony document.');
270
+ }
271
+
272
+ let origin: URL;
273
+ try {
274
+ origin = new URL(valueArg.origin);
275
+ } catch {
276
+ throw new Error('Invalid WebAuthn ceremony origin.');
277
+ }
278
+ const hostname = origin.hostname.toLowerCase().replace(/\.$/, '');
279
+ if (
280
+ origin.origin !== valueArg.origin
281
+ || (origin.protocol !== 'http:' && origin.protocol !== 'https:')
282
+ || (hostname !== valueArg.rpId && !hostname.endsWith(`.${valueArg.rpId}`))
283
+ ) {
284
+ throw new Error('Invalid WebAuthn ceremony origin or RP ID.');
285
+ }
286
+
287
+ if (
288
+ valueArg.kind === 'setup'
289
+ && (!isBase64Url(valueArg.setupGeneration, 32) || !isBase64Url(valueArg.setupHash, 32))
290
+ ) {
291
+ throw new Error('Invalid WebAuthn setup ceremony authority.');
292
+ }
293
+ };
294
+
295
+ export const assertControllerAuditEventDocument = (
296
+ valueArg: unknown,
297
+ ): asserts valueArg is IControllerAuditEvent => {
298
+ if (!isPlainObject(valueArg)) throw new Error('Invalid controller audit event.');
299
+ if (
300
+ !hasExactKeys(
301
+ valueArg,
302
+ ['id', 'controllerId', 'timestamp', 'type', 'outcome'],
303
+ ['operationId', 'peerId', 'credentialId', 'sessionId', 'requestId'],
304
+ )
305
+ || !isBase64Url(valueArg.id, 32)
306
+ || !isNonEmptyString(valueArg.controllerId)
307
+ || !/^controller:[1-9][0-9]{0,4}$/.test(valueArg.controllerId)
308
+ || Number(valueArg.controllerId.slice('controller:'.length)) > 65535
309
+ || !isValidDate(valueArg.timestamp)
310
+ || !auditEventTypes.has(valueArg.type as string)
311
+ || !auditOutcomes.has(valueArg.outcome as string)
312
+ || (
313
+ Object.hasOwn(valueArg, 'operationId')
314
+ && !isBase64Url(valueArg.operationId, valueArg.type === 'resource.browser.operation' ? 18 : 24)
315
+ )
316
+ ) {
317
+ throw new Error('Invalid controller audit event.');
318
+ }
319
+ for (const optionalKey of [
320
+ 'peerId',
321
+ 'credentialId',
322
+ 'sessionId',
323
+ 'requestId',
324
+ ] as const) {
325
+ if (
326
+ Object.hasOwn(valueArg, optionalKey)
327
+ && !(
328
+ // Protocol v5 audit rows are retained until TTL expiry; all new writes
329
+ // are qualified by IRecordAuditEventInput.
330
+ (isNonEmptyString(valueArg[optionalKey]) && valueArg[optionalKey].length <= 512)
331
+ || isRuntimeId(valueArg[optionalKey], ['opencode', 'flex', 'controller'])
332
+ )
333
+ ) {
334
+ throw new Error('Invalid controller audit event identifier.');
335
+ }
336
+ }
337
+ };
338
+
339
+ // A project name is a display path: relative to the projects root for
340
+ // directories inside it, else the absolute directory path. Absolute names may
341
+ // contain hidden (dot-prefixed) segments because they were typed explicitly;
342
+ // relative names stay restricted to visible directory names.
343
+ export const isProjectRelativePathName = (valueArg: unknown): valueArg is string => {
344
+ if (typeof valueArg !== 'string' || valueArg.length < 1 || valueArg.length > 4096) {
345
+ return false;
346
+ }
347
+ const absolute = valueArg.startsWith('/');
348
+ const segments = absolute ? valueArg.slice(1).split('/') : valueArg.split('/');
349
+ return segments.every((segment) => (
350
+ segment.length > 0
351
+ && segment.length <= 255
352
+ && segment !== '.'
353
+ && segment !== '..'
354
+ && (absolute || !segment.startsWith('.'))
355
+ // eslint-disable-next-line no-control-regex
356
+ && !/[\x00-\x1f\x7f\\]/.test(segment)
357
+ ));
358
+ };
359
+ const controllerIdPattern = /^controller:[1-9][0-9]{0,4}$/;
360
+
361
+ const isControllerId = (valueArg: unknown): valueArg is string =>
362
+ isNonEmptyString(valueArg)
363
+ && controllerIdPattern.test(valueArg)
364
+ && Number(valueArg.slice('controller:'.length)) <= 65535;
365
+
366
+ const isFilesystemIdentityPart = (valueArg: unknown): valueArg is string => (
367
+ typeof valueArg === 'string'
368
+ && /^(?:0|[1-9][0-9]{0,31})$/u.test(valueArg)
369
+ );
370
+
371
+ const isFilesystemIdentity = (
372
+ valueArg: unknown,
373
+ pathArg: string,
374
+ targetTypeArg: 'directory' | 'file',
375
+ ): valueArg is IControllerFilesystemIdentity => {
376
+ if (!isPlainObject(valueArg) || !hasExactKeys(valueArg, ['ancestry'])) return false;
377
+ if (
378
+ !Array.isArray(valueArg.ancestry)
379
+ || valueArg.ancestry.length < 1
380
+ || valueArg.ancestry.length > controllerFilesystemIdentityMaximumComponents
381
+ ) return false;
382
+ const root = plugins.path.parse(pathArg).root;
383
+ const relative = pathArg.slice(root.length);
384
+ const expectedLength = 1 + relative.split(plugins.path.sep).filter(Boolean).length;
385
+ const ancestry = valueArg.ancestry;
386
+ if (ancestry.length !== expectedLength) return false;
387
+ return ancestry.every((component, index) => (
388
+ isPlainObject(component)
389
+ && hasExactKeys(component, ['device', 'inode', 'type'])
390
+ && isFilesystemIdentityPart(component.device)
391
+ && isFilesystemIdentityPart(component.inode)
392
+ && component.type === (index === ancestry.length - 1 ? targetTypeArg : 'directory')
393
+ ));
394
+ };
395
+
396
+ export function assertControllerProjectDocument(
397
+ valueArg: unknown,
398
+ ): asserts valueArg is IControllerProjectDocument {
399
+ if (!isPlainObject(valueArg)) throw new Error('Invalid controller project document.');
400
+ if (
401
+ !hasExactKeys(
402
+ valueArg,
403
+ ['id', 'controllerId', 'name', 'directory', 'directoryIdentityState', 'createdAt'],
404
+ ['directoryIdentity', 'removalStartedAt', 'removedAt'],
405
+ )
406
+ || !isBase64Url(valueArg.id, 16)
407
+ || !isControllerId(valueArg.controllerId)
408
+ || !isProjectRelativePathName(valueArg.name)
409
+ || !isNonEmptyString(valueArg.directory)
410
+ || !plugins.path.isAbsolute(valueArg.directory)
411
+ || Buffer.byteLength(valueArg.directory, 'utf8') > 4096
412
+ || (
413
+ valueArg.directoryIdentityState !== 'bound'
414
+ && valueArg.directoryIdentityState !== 'unbound'
415
+ )
416
+ || (
417
+ valueArg.directoryIdentityState === 'bound'
418
+ && !isFilesystemIdentity(valueArg.directoryIdentity, valueArg.directory, 'directory')
419
+ )
420
+ || (
421
+ valueArg.directoryIdentityState === 'unbound'
422
+ && Object.hasOwn(valueArg, 'directoryIdentity')
423
+ )
424
+ || !isValidDate(valueArg.createdAt)
425
+ || (Object.hasOwn(valueArg, 'removalStartedAt') && !isValidDate(valueArg.removalStartedAt))
426
+ || (Object.hasOwn(valueArg, 'removedAt') && !isValidDate(valueArg.removedAt))
427
+ || (
428
+ Object.hasOwn(valueArg, 'removalStartedAt')
429
+ && Object.hasOwn(valueArg, 'removedAt')
430
+ )
431
+ ) {
432
+ throw new Error('Invalid controller project document.');
433
+ }
434
+ }
435
+
436
+ const isModelChoice = (valueArg: unknown): valueArg is TControllerModelChoice => {
437
+ if (!isPlainObject(valueArg)) return false;
438
+ const hasValidVariant = !Object.hasOwn(valueArg, 'variant')
439
+ || (
440
+ isNonEmptyString(valueArg.variant)
441
+ && valueArg.variant.length <= 64
442
+ && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(valueArg.variant)
443
+ );
444
+ if (
445
+ !hasValidVariant
446
+ || !isNonEmptyString(valueArg.providerID)
447
+ || valueArg.providerID.length > 256
448
+ || !isNonEmptyString(valueArg.modelID)
449
+ || valueArg.modelID.length > 256
450
+ ) {
451
+ return false;
452
+ }
453
+ if (valueArg.harnessId === 'opencode') {
454
+ return hasExactKeys(valueArg, ['harnessId', 'providerID', 'modelID'], ['variant']);
455
+ }
456
+ return valueArg.harnessId === 'flex'
457
+ && hasExactKeys(
458
+ valueArg,
459
+ ['harnessId', 'providerConnectionId', 'providerID', 'modelID'],
460
+ ['variant'],
461
+ )
462
+ && isNonEmptyString(valueArg.providerConnectionId)
463
+ && Buffer.byteLength(valueArg.providerConnectionId, 'utf8') <= 512;
464
+ };
465
+
466
+ export function assertControllerSettingsDocument(
467
+ valueArg: unknown,
468
+ ): asserts valueArg is IControllerSettingsDocument {
469
+ if (!isPlainObject(valueArg)) throw new Error('Invalid controller settings document.');
470
+ if (
471
+ !hasExactKeys(
472
+ valueArg,
473
+ ['id', 'controllerId', 'defaultModels'],
474
+ ['autoAcceptPermissions', 'selectedOpenCodeProviderConnectionId'],
475
+ )
476
+ || !isControllerId(valueArg.controllerId)
477
+ || valueArg.id !== `${valueArg.controllerId}:settings`
478
+ || !Array.isArray(valueArg.defaultModels)
479
+ || valueArg.defaultModels.length > 2
480
+ || !valueArg.defaultModels.every(isModelChoice)
481
+ || new Set(valueArg.defaultModels.map(
482
+ (model) => (model as TControllerModelChoice).harnessId,
483
+ )).size
484
+ !== valueArg.defaultModels.length
485
+ // Disabled is represented by an absent key, never by a stored false.
486
+ || (Object.hasOwn(valueArg, 'autoAcceptPermissions') && valueArg.autoAcceptPermissions !== true)
487
+ || (valueArg.selectedOpenCodeProviderConnectionId !== undefined && (
488
+ !isNonEmptyString(valueArg.selectedOpenCodeProviderConnectionId)
489
+ || Buffer.byteLength(valueArg.selectedOpenCodeProviderConnectionId, 'utf8') > 512
490
+ ))
491
+ ) {
492
+ throw new Error('Invalid controller settings document.');
493
+ }
494
+ }
495
+
496
+ @plugins.smartdata.managed({ collectionName: 'opencode_controller_auth' })
497
+ @plugins.smartdata.exactPersistence({
498
+ assertDocument: assertControllerAuthDocument,
499
+ })
500
+ export class ControllerAuthModel
501
+ extends plugins.smartdata.SmartDataDbDoc<ControllerAuthModel, IControllerAuthDocument> {
502
+ declare static exact: plugins.smartdata.TExact<ControllerAuthModel>;
503
+
504
+ @plugins.smartdata.unI()
505
+ public id!: string;
506
+
507
+ @plugins.smartdata.svDb()
508
+ public config!: IControllerRuntimeConfig;
509
+
510
+ @plugins.smartdata.svDb()
511
+ public authState!: IControllerAuthDocument['authState'];
512
+
513
+ @plugins.smartdata.svDb()
514
+ public setup!: ISetupAuthorityState | null;
515
+
516
+ @plugins.smartdata.svDb()
517
+ public webauthnUserId!: string;
518
+
519
+ @plugins.smartdata.svDb()
520
+ public credentials!: IStoredPasskeyCredential[];
521
+ }
522
+
523
+ @plugins.smartdata.managed({ collectionName: 'opencode_controller_webauthn_ceremonies' })
524
+ @plugins.smartdata.exactPersistence({
525
+ assertDocument: assertWebAuthnCeremonyDocument,
526
+ })
527
+ export class WebAuthnCeremonyModel
528
+ extends plugins.smartdata.SmartDataDbDoc<WebAuthnCeremonyModel, IWebAuthnCeremonyDocument> {
529
+ declare static exact: plugins.smartdata.TExact<WebAuthnCeremonyModel>;
530
+
531
+ @plugins.smartdata.unI()
532
+ public id!: string;
533
+
534
+ @plugins.smartdata.svDb()
535
+ public kind!: IWebAuthnCeremonyDocument['kind'];
536
+
537
+ @plugins.smartdata.svDb()
538
+ public state!: IWebAuthnCeremonyDocument['state'];
539
+
540
+ @plugins.smartdata.svDb()
541
+ public peerId!: string;
542
+
543
+ @plugins.smartdata.svDb()
544
+ public challenge!: string;
545
+
546
+ @plugins.smartdata.svDb()
547
+ public origin!: string;
548
+
549
+ @plugins.smartdata.svDb()
550
+ public rpId!: string;
551
+
552
+ @plugins.smartdata.svDb()
553
+ public userId!: string;
554
+
555
+ @plugins.smartdata.svDb()
556
+ public setupGeneration?: string;
557
+
558
+ @plugins.smartdata.svDb()
559
+ public setupHash?: string;
560
+
561
+ @plugins.smartdata.svDb()
562
+ public createdAt!: Date;
563
+
564
+ @plugins.smartdata.index({ expireAfterSeconds: 0 })
565
+ public expiresAt!: Date;
566
+
567
+ @plugins.smartdata.svDb()
568
+ public consumedAt?: Date;
569
+
570
+ @plugins.smartdata.svDb()
571
+ public consumedNonce?: string;
572
+ }
573
+
574
+ @plugins.smartdata.managed({ collectionName: 'opencode_controller_projects' })
575
+ @plugins.smartdata.exactPersistence({
576
+ assertDocument: assertControllerProjectDocument,
577
+ })
578
+ export class ControllerProjectModel
579
+ extends plugins.smartdata.SmartDataDbDoc<ControllerProjectModel, IControllerProjectDocument> {
580
+ declare static exact: plugins.smartdata.TExact<ControllerProjectModel>;
581
+
582
+ @plugins.smartdata.unI()
583
+ public id!: string;
584
+
585
+ @plugins.smartdata.svDb()
586
+ public controllerId!: string;
587
+
588
+ // `name` shadows a base-class property; the initializer makes that explicit.
589
+ @plugins.smartdata.svDb()
590
+ public name: string = '';
591
+
592
+ @plugins.smartdata.svDb()
593
+ public directory!: string;
594
+
595
+ @plugins.smartdata.svDb()
596
+ public directoryIdentityState!: IControllerProjectDocument['directoryIdentityState'];
597
+
598
+ @plugins.smartdata.svDb()
599
+ public directoryIdentity?: IControllerProjectDocument['directoryIdentity'];
600
+
601
+ @plugins.smartdata.svDb()
602
+ public createdAt!: Date;
603
+
604
+ @plugins.smartdata.svDb()
605
+ public removalStartedAt?: Date;
606
+
607
+ // Deregistered projects expire from the collection after 90 days; the
608
+ // directory on disk is never touched.
609
+ @plugins.smartdata.index({ expireAfterSeconds: 90 * 24 * 60 * 60 })
610
+ public removedAt?: Date;
611
+ }
612
+
613
+ @plugins.smartdata.managed({ collectionName: 'opencode_controller_settings' })
614
+ @plugins.smartdata.exactPersistence({
615
+ assertDocument: assertControllerSettingsDocument,
616
+ })
617
+ export class ControllerSettingsModel
618
+ extends plugins.smartdata.SmartDataDbDoc<ControllerSettingsModel, IControllerSettingsDocument> {
619
+ declare static exact: plugins.smartdata.TExact<ControllerSettingsModel>;
620
+
621
+ @plugins.smartdata.unI()
622
+ public id!: string;
623
+
624
+ @plugins.smartdata.svDb()
625
+ public controllerId!: string;
626
+
627
+ @plugins.smartdata.svDb()
628
+ public defaultModels!: TControllerModelChoice[];
629
+
630
+ @plugins.smartdata.svDb()
631
+ public autoAcceptPermissions?: true;
632
+
633
+ @plugins.smartdata.svDb()
634
+ public selectedOpenCodeProviderConnectionId?: string;
635
+ }
636
+
637
+ const maxSessionGroups = 64;
638
+ const maxSessionsPerGroup = 512;
639
+ const maxUngroupedSessions = 2048;
640
+ const groupIdentifierPattern = /^[A-Za-z0-9_-]{1,128}$/;
641
+
642
+ export const isSessionGroupList = (
643
+ valueArg: unknown,
644
+ ): valueArg is IControllerSessionGroupsDocument['groups'] => {
645
+ if (!Array.isArray(valueArg) || valueArg.length > maxSessionGroups) return false;
646
+ const groupIds = new Set<string>();
647
+ const memberIds = new Set<string>();
648
+ for (const group of valueArg) {
649
+ if (
650
+ !isPlainObject(group)
651
+ || !hasExactKeys(group, ['id', 'name', 'sessionIds'])
652
+ || !isNonEmptyString(group.id)
653
+ || !groupIdentifierPattern.test(group.id)
654
+ || groupIds.has(group.id)
655
+ || !isNonEmptyString(group.name)
656
+ || group.name.length > 128
657
+ // eslint-disable-next-line no-control-regex
658
+ || /[\x00-\x1f\x7f]/.test(group.name)
659
+ || !Array.isArray(group.sessionIds)
660
+ || group.sessionIds.length > maxSessionsPerGroup
661
+ ) {
662
+ return false;
663
+ }
664
+ groupIds.add(group.id);
665
+ for (const sessionId of group.sessionIds) {
666
+ if (
667
+ !isRuntimeId(sessionId, ['opencode', 'flex'])
668
+ // A session belongs to at most one group; duplicates are invalid.
669
+ || memberIds.has(controllerRuntimeIdKey(sessionId))
670
+ ) {
671
+ return false;
672
+ }
673
+ memberIds.add(controllerRuntimeIdKey(sessionId));
674
+ }
675
+ }
676
+ return true;
677
+ };
678
+
679
+ export const isUngroupedSessionList = (
680
+ valueArg: unknown,
681
+ groupedSessionIdsArg: ReadonlySet<string> = new Set(),
682
+ ): valueArg is TControllerSessionId[] => {
683
+ if (!Array.isArray(valueArg) || valueArg.length > maxUngroupedSessions) return false;
684
+ const sessionIds = new Set<string>();
685
+ for (const sessionId of valueArg) {
686
+ const key = isRuntimeId(sessionId, ['opencode', 'flex'])
687
+ ? controllerRuntimeIdKey(sessionId)
688
+ : undefined;
689
+ if (
690
+ key === undefined
691
+ || groupedSessionIdsArg.has(key)
692
+ || sessionIds.has(key)
693
+ ) {
694
+ return false;
695
+ }
696
+ sessionIds.add(key);
697
+ }
698
+ return true;
699
+ };
700
+
701
+ export function assertControllerSessionGroupsDocument(
702
+ valueArg: unknown,
703
+ ): asserts valueArg is IControllerSessionGroupsDocument {
704
+ if (!isPlainObject(valueArg)) throw new Error('Invalid controller session groups document.');
705
+ if (
706
+ !hasExactKeys(
707
+ valueArg,
708
+ ['id', 'controllerId', 'projectId', 'groups'],
709
+ ['ungroupedSessionIds', 'revision'],
710
+ )
711
+ || !isControllerId(valueArg.controllerId)
712
+ || !isNonEmptyString(valueArg.projectId)
713
+ || !groupIdentifierPattern.test(valueArg.projectId)
714
+ || valueArg.id !== `${valueArg.controllerId}:groups:${valueArg.projectId}`
715
+ || !isSessionGroupList(valueArg.groups)
716
+ ) {
717
+ throw new Error('Invalid controller session groups document.');
718
+ }
719
+ const groupedSessionIds = new Set(
720
+ valueArg.groups.flatMap((group) => group.sessionIds.map(controllerRuntimeIdKey)),
721
+ );
722
+ const hasUngroupedSessionIds = valueArg.ungroupedSessionIds !== undefined;
723
+ const hasRevision = valueArg.revision !== undefined;
724
+ if (
725
+ hasUngroupedSessionIds !== hasRevision
726
+ ||
727
+ (
728
+ hasRevision
729
+ && (!Number.isSafeInteger(valueArg.revision) || (valueArg.revision as number) < 0)
730
+ )
731
+ ||
732
+ hasUngroupedSessionIds
733
+ && !isUngroupedSessionList(valueArg.ungroupedSessionIds, groupedSessionIds)
734
+ ) {
735
+ throw new Error('Invalid controller session groups document.');
736
+ }
737
+ }
738
+
739
+ @plugins.smartdata.managed({ collectionName: 'opencode_controller_session_groups' })
740
+ @plugins.smartdata.exactPersistence({
741
+ assertDocument: assertControllerSessionGroupsDocument,
742
+ })
743
+ export class ControllerSessionGroupsModel
744
+ extends plugins.smartdata.SmartDataDbDoc<ControllerSessionGroupsModel, IControllerSessionGroupsDocument> {
745
+ declare static exact: plugins.smartdata.TExact<ControllerSessionGroupsModel>;
746
+
747
+ @plugins.smartdata.unI()
748
+ public id!: string;
749
+
750
+ @plugins.smartdata.svDb()
751
+ public controllerId!: string;
752
+
753
+ @plugins.smartdata.svDb()
754
+ public projectId!: string;
755
+
756
+ @plugins.smartdata.svDb()
757
+ public groups!: IControllerSessionGroupsDocument['groups'];
758
+
759
+ @plugins.smartdata.svDb()
760
+ public ungroupedSessionIds?: IControllerSessionGroupsDocument['ungroupedSessionIds'];
761
+
762
+ @plugins.smartdata.svDb()
763
+ public revision?: number;
764
+ }
765
+
766
+ const resourceIdPattern = /^[A-Za-z0-9_-]{22}$/;
767
+ const resourceAuthorityPattern = /^[A-Za-z0-9_-]{43}$/;
768
+
769
+ export function assertControllerResourceDocument(
770
+ valueArg: unknown,
771
+ ): asserts valueArg is IControllerResourceDocument {
772
+ if (!isPlainObject(valueArg)) throw new Error('Invalid controller resource document.');
773
+ if (
774
+ !hasExactKeys(
775
+ valueArg,
776
+ [
777
+ 'id',
778
+ 'controllerId',
779
+ 'projectId',
780
+ 'kind',
781
+ 'title',
782
+ 'attachmentAuthorityId',
783
+ 'attachmentRevision',
784
+ 'sessionId',
785
+ 'lifecycle',
786
+ 'createdAt',
787
+ 'updatedAt',
788
+ 'updateId',
789
+ ],
790
+ ['pendingAttachment', 'terminal', 'retiringAt', 'retiredAt'],
791
+ )
792
+ || !resourceIdPattern.test(valueArg.id as string)
793
+ || !isControllerId(valueArg.controllerId)
794
+ || !isNonEmptyString(valueArg.projectId)
795
+ || !groupIdentifierPattern.test(valueArg.projectId)
796
+ || (valueArg.kind !== 'terminal' && valueArg.kind !== 'browser')
797
+ || !isNonEmptyString(valueArg.title)
798
+ || Buffer.byteLength(valueArg.title, 'utf8') > 256
799
+ // eslint-disable-next-line no-control-regex
800
+ || /[\x00-\x1f\x7f]/.test(valueArg.title as string)
801
+ || !resourceAuthorityPattern.test(valueArg.attachmentAuthorityId as string)
802
+ || !Number.isSafeInteger(valueArg.attachmentRevision)
803
+ || (valueArg.attachmentRevision as number) < 0
804
+ || (valueArg.sessionId !== null && !isRuntimeId(valueArg.sessionId, ['opencode', 'flex']))
805
+ || !['active', 'retiring', 'retired'].includes(valueArg.lifecycle as string)
806
+ || !isValidDate(valueArg.createdAt)
807
+ || !isValidDate(valueArg.updatedAt)
808
+ || valueArg.updatedAt.getTime() < valueArg.createdAt.getTime()
809
+ || !isBase64Url(valueArg.updateId, 32)
810
+ ) {
811
+ throw new Error('Invalid controller resource document.');
812
+ }
813
+
814
+ if (valueArg.pendingAttachment !== undefined) {
815
+ const pending = valueArg.pendingAttachment;
816
+ if (
817
+ !isPlainObject(pending)
818
+ || !hasExactKeys(pending, [
819
+ 'operationId',
820
+ 'attachmentRevision',
821
+ 'sessionId',
822
+ 'requestedAt',
823
+ ])
824
+ || !isBase64Url(pending.operationId, 16)
825
+ || pending.attachmentRevision !== (valueArg.attachmentRevision as number) + 1
826
+ || (pending.sessionId !== null && !isRuntimeId(pending.sessionId, ['opencode', 'flex']))
827
+ || !isValidDate(pending.requestedAt)
828
+ || valueArg.lifecycle !== 'active'
829
+ ) {
830
+ throw new Error('Invalid pending controller resource attachment.');
831
+ }
832
+ }
833
+
834
+ if (valueArg.kind === 'terminal') {
835
+ if (!isPlainObject(valueArg.terminal) || !hasExactKeys(
836
+ valueArg.terminal,
837
+ ['command', 'args', 'cwd'],
838
+ ['stoppedAt', 'lastExitCode'],
839
+ ) || !isNonEmptyString(valueArg.terminal.command)
840
+ || !plugins.path.isAbsolute(valueArg.terminal.command)
841
+ || !Array.isArray(valueArg.terminal.args)
842
+ || valueArg.terminal.args.length > 32
843
+ || valueArg.terminal.args.some((arg) => (
844
+ typeof arg !== 'string' || Buffer.byteLength(arg, 'utf8') > 4096
845
+ ))
846
+ || !isNonEmptyString(valueArg.terminal.cwd)
847
+ || !plugins.path.isAbsolute(valueArg.terminal.cwd)
848
+ || (valueArg.terminal.stoppedAt !== undefined && !isValidDate(valueArg.terminal.stoppedAt))
849
+ || (valueArg.terminal.lastExitCode !== undefined
850
+ && (!Number.isSafeInteger(valueArg.terminal.lastExitCode)
851
+ || Math.abs(valueArg.terminal.lastExitCode as number) > 255))) {
852
+ throw new Error('Invalid controller terminal resource metadata.');
853
+ }
854
+ } else if (valueArg.terminal !== undefined) {
855
+ throw new Error('Browser resources cannot retain terminal metadata.');
856
+ }
857
+
858
+ const hasRetiringAt = valueArg.retiringAt !== undefined;
859
+ const hasRetiredAt = valueArg.retiredAt !== undefined;
860
+ if (
861
+ (hasRetiringAt && !isValidDate(valueArg.retiringAt))
862
+ || (hasRetiredAt && !isValidDate(valueArg.retiredAt))
863
+ || (valueArg.lifecycle === 'active' && (hasRetiringAt || hasRetiredAt))
864
+ || (valueArg.lifecycle === 'retiring' && (!hasRetiringAt || hasRetiredAt))
865
+ || (valueArg.lifecycle === 'retired' && (!hasRetiringAt || !hasRetiredAt))
866
+ ) {
867
+ throw new Error('Invalid controller resource lifecycle.');
868
+ }
869
+ }
870
+
871
+ @plugins.smartdata.managed({ collectionName: 'opencode_controller_resources' })
872
+ @plugins.smartdata.exactPersistence({
873
+ assertDocument: assertControllerResourceDocument,
874
+ })
875
+ export class ControllerResourceModel
876
+ extends plugins.smartdata.SmartDataDbDoc<ControllerResourceModel, IControllerResourceDocument> {
877
+ declare static exact: plugins.smartdata.TExact<ControllerResourceModel>;
878
+
879
+ @plugins.smartdata.unI()
880
+ public id!: string;
881
+
882
+ @plugins.smartdata.svDb()
883
+ public controllerId!: string;
884
+
885
+ @plugins.smartdata.svDb()
886
+ public projectId!: string;
887
+
888
+ @plugins.smartdata.svDb()
889
+ public kind!: IControllerResourceDocument['kind'];
890
+
891
+ @plugins.smartdata.svDb()
892
+ public title: string = '';
893
+
894
+ @plugins.smartdata.svDb()
895
+ public attachmentAuthorityId!: string;
896
+
897
+ @plugins.smartdata.svDb()
898
+ public attachmentRevision!: number;
899
+
900
+ @plugins.smartdata.svDb()
901
+ public sessionId!: IControllerResourceDocument['sessionId'];
902
+
903
+ @plugins.smartdata.svDb()
904
+ public pendingAttachment?: IControllerResourceDocument['pendingAttachment'];
905
+
906
+ @plugins.smartdata.svDb()
907
+ public lifecycle!: IControllerResourceDocument['lifecycle'];
908
+
909
+ @plugins.smartdata.svDb()
910
+ public terminal?: IControllerResourceDocument['terminal'];
911
+
912
+ @plugins.smartdata.svDb()
913
+ public createdAt!: Date;
914
+
915
+ @plugins.smartdata.svDb()
916
+ public updatedAt!: Date;
917
+
918
+ @plugins.smartdata.svDb()
919
+ public retiringAt?: Date;
920
+
921
+ @plugins.smartdata.index({ expireAfterSeconds: 90 * 24 * 60 * 60 })
922
+ public retiredAt?: Date;
923
+
924
+ @plugins.smartdata.svDb()
925
+ public updateId!: string;
926
+ }
927
+
928
+ const maxSessionStateExchanges = 8;
929
+ const maxSessionStateExchangeBytes = 256 * 1024;
930
+
931
+ export const controllerSessionStateId = (
932
+ controllerIdArg: string,
933
+ projectIdArg: string,
934
+ sessionIdArg: IControllerRuntimeId,
935
+ ): string => `sessionstate:v1:${plugins.crypto
936
+ .createHash('sha256')
937
+ .update(JSON.stringify([
938
+ controllerIdArg,
939
+ projectIdArg,
940
+ sessionIdArg.harnessId,
941
+ sessionIdArg.nativeId,
942
+ ]))
943
+ .digest('base64url')}`;
944
+
945
+ export function assertControllerSessionStateDocument(
946
+ valueArg: unknown,
947
+ ): asserts valueArg is IControllerSessionStateDocument {
948
+ if (!isPlainObject(valueArg)) throw new Error('Invalid controller session state document.');
949
+ if (
950
+ !hasExactKeys(
951
+ valueArg,
952
+ [
953
+ 'id',
954
+ 'controllerId',
955
+ 'projectId',
956
+ 'sessionId',
957
+ 'scratchpad',
958
+ 'intelligenceExchanges',
959
+ 'updateId',
960
+ ],
961
+ ['modelChoice', 'flexProjectManagement', 'deletedAt'],
962
+ )
963
+ || !isControllerId(valueArg.controllerId)
964
+ || !isNonEmptyString(valueArg.projectId)
965
+ || !groupIdentifierPattern.test(valueArg.projectId)
966
+ || !isRuntimeId(valueArg.sessionId, ['opencode', 'flex'])
967
+ || (valueArg.modelChoice !== undefined && (
968
+ !isModelChoice(valueArg.modelChoice)
969
+ || valueArg.modelChoice.harnessId !== valueArg.sessionId.harnessId
970
+ ))
971
+ || valueArg.id !== controllerSessionStateId(
972
+ valueArg.controllerId,
973
+ valueArg.projectId,
974
+ valueArg.sessionId,
975
+ )
976
+ || !isBase64Url(valueArg.updateId, 32)
977
+ || (Object.hasOwn(valueArg, 'deletedAt') && !isValidDate(valueArg.deletedAt))
978
+ || !isPlainObject(valueArg.scratchpad)
979
+ || !hasExactKeys(
980
+ valueArg.scratchpad,
981
+ ['text', 'revision'],
982
+ ['updatedAt', 'updatedBy'],
983
+ )
984
+ || typeof valueArg.scratchpad.text !== 'string'
985
+ || valueArg.scratchpad.text.length > 32_768
986
+ || Buffer.byteLength(valueArg.scratchpad.text, 'utf8') > 128 * 1024
987
+ || !Number.isSafeInteger(valueArg.scratchpad.revision)
988
+ || (valueArg.scratchpad.revision as number) < 0
989
+ ) {
990
+ throw new Error('Invalid controller session state document.');
991
+ }
992
+
993
+ const hasUpdatedAt = Object.hasOwn(valueArg.scratchpad, 'updatedAt');
994
+ const hasUpdatedBy = Object.hasOwn(valueArg.scratchpad, 'updatedBy');
995
+ if (
996
+ hasUpdatedAt !== hasUpdatedBy
997
+ || (hasUpdatedAt && !isValidDate(valueArg.scratchpad.updatedAt))
998
+ || (
999
+ hasUpdatedBy
1000
+ && valueArg.scratchpad.updatedBy !== 'user'
1001
+ && valueArg.scratchpad.updatedBy !== 'intelligence'
1002
+ && valueArg.scratchpad.updatedBy !== 'agent'
1003
+ && valueArg.scratchpad.updatedBy !== 'application'
1004
+ )
1005
+ || ((valueArg.scratchpad.revision as number) === 0 && hasUpdatedAt)
1006
+ || ((valueArg.scratchpad.revision as number) > 0 && !hasUpdatedAt)
1007
+ || !Array.isArray(valueArg.intelligenceExchanges)
1008
+ || valueArg.intelligenceExchanges.length > maxSessionStateExchanges
1009
+ || Buffer.byteLength(JSON.stringify(valueArg.intelligenceExchanges), 'utf8')
1010
+ > maxSessionStateExchangeBytes
1011
+ ) {
1012
+ throw new Error('Invalid controller session state document.');
1013
+ }
1014
+
1015
+ const exchangeIds = new Set<string>();
1016
+ for (const exchange of valueArg.intelligenceExchanges) {
1017
+ if (
1018
+ !isPlainObject(exchange)
1019
+ || !hasExactKeys(
1020
+ exchange,
1021
+ ['id', 'question', 'status', 'createdAt'],
1022
+ [
1023
+ 'answer',
1024
+ 'error',
1025
+ 'model',
1026
+ 'scratchpadConflict',
1027
+ 'scratchpadRevisionAtAdmission',
1028
+ 'temporarySessionId',
1029
+ 'completedAt',
1030
+ ],
1031
+ )
1032
+ || !isBase64Url(exchange.id, 16)
1033
+ || exchangeIds.has(exchange.id)
1034
+ || !isNonEmptyString(exchange.question)
1035
+ || exchange.question.length > 8_192
1036
+ || Buffer.byteLength(exchange.question, 'utf8') > 32 * 1024
1037
+ || !['running', 'completed', 'error'].includes(exchange.status as string)
1038
+ || !isValidDate(exchange.createdAt)
1039
+ || (Object.hasOwn(exchange, 'completedAt') && !isValidDate(exchange.completedAt))
1040
+ || (Object.hasOwn(exchange, 'answer')
1041
+ && (typeof exchange.answer !== 'string'
1042
+ || exchange.answer.length > 32_768
1043
+ || Buffer.byteLength(exchange.answer, 'utf8') > 128 * 1024))
1044
+ || (Object.hasOwn(exchange, 'error')
1045
+ && (typeof exchange.error !== 'string'
1046
+ || exchange.error.length < 1
1047
+ || Buffer.byteLength(exchange.error, 'utf8') > 16 * 1024))
1048
+ || (Object.hasOwn(exchange, 'model')
1049
+ && (!isNonEmptyString(exchange.model)
1050
+ || Buffer.byteLength(exchange.model, 'utf8') > 512))
1051
+ || (Object.hasOwn(exchange, 'scratchpadConflict') && exchange.scratchpadConflict !== true)
1052
+ || (Object.hasOwn(exchange, 'scratchpadRevisionAtAdmission') && (
1053
+ !Number.isSafeInteger(exchange.scratchpadRevisionAtAdmission)
1054
+ || (exchange.scratchpadRevisionAtAdmission as number) < 0
1055
+ ))
1056
+ || (Object.hasOwn(exchange, 'temporarySessionId') && (
1057
+ !isNonEmptyString(exchange.temporarySessionId)
1058
+ || Buffer.byteLength(exchange.temporarySessionId, 'utf8') > 512
1059
+ ))
1060
+ ) {
1061
+ throw new Error('Invalid controller session intelligence exchange.');
1062
+ }
1063
+ exchangeIds.add(exchange.id);
1064
+ if (
1065
+ (exchange.status === 'running' && (
1066
+ Object.hasOwn(exchange, 'answer')
1067
+ || Object.hasOwn(exchange, 'error')
1068
+ || Object.hasOwn(exchange, 'completedAt')
1069
+ || Object.hasOwn(exchange, 'scratchpadConflict')
1070
+ || !Number.isSafeInteger(exchange.scratchpadRevisionAtAdmission)
1071
+ ))
1072
+ || (exchange.status === 'completed' && (
1073
+ typeof exchange.answer !== 'string'
1074
+ || !isValidDate(exchange.completedAt)
1075
+ || Object.hasOwn(exchange, 'error')
1076
+ || Object.hasOwn(exchange, 'scratchpadRevisionAtAdmission')
1077
+ ))
1078
+ || (exchange.status === 'error' && (
1079
+ !isNonEmptyString(exchange.error)
1080
+ || !isValidDate(exchange.completedAt)
1081
+ || Object.hasOwn(exchange, 'answer')
1082
+ || Object.hasOwn(exchange, 'scratchpadConflict')
1083
+ || Object.hasOwn(exchange, 'scratchpadRevisionAtAdmission')
1084
+ ))
1085
+ ) {
1086
+ throw new Error('Invalid controller session intelligence exchange state.');
1087
+ }
1088
+ }
1089
+
1090
+ if (valueArg.flexProjectManagement !== undefined) {
1091
+ const projectManagement = valueArg.flexProjectManagement;
1092
+ plugins.flexharness.assertFlexProjectManagementRecord(projectManagement);
1093
+ const isTombstone = 'deletedAt' in projectManagement;
1094
+ const deletedAt = valueArg.deletedAt as Date | undefined;
1095
+ if (
1096
+ valueArg.sessionId.harnessId !== 'flex'
1097
+ || isTombstone !== (deletedAt !== undefined)
1098
+ || (
1099
+ !isTombstone
1100
+ && projectManagement.scratchpad !== valueArg.scratchpad.text
1101
+ )
1102
+ || (
1103
+ isTombstone
1104
+ && projectManagement.deletedAt !== deletedAt?.toISOString()
1105
+ )
1106
+ ) {
1107
+ throw new Error('Invalid controller Flex project-management state.');
1108
+ }
1109
+ }
1110
+
1111
+ if (
1112
+ valueArg.deletedAt !== undefined
1113
+ && (
1114
+ valueArg.scratchpad.text !== ''
1115
+ || valueArg.intelligenceExchanges.length !== 0
1116
+ || valueArg.modelChoice !== undefined
1117
+ )
1118
+ ) {
1119
+ throw new Error('Deleted controller session state must not retain content.');
1120
+ }
1121
+ }
1122
+
1123
+ @plugins.smartdata.managed({ collectionName: 'opencode_controller_session_states' })
1124
+ @plugins.smartdata.exactPersistence({
1125
+ assertDocument: assertControllerSessionStateDocument,
1126
+ })
1127
+ export class ControllerSessionStateModel
1128
+ extends plugins.smartdata.SmartDataDbDoc<ControllerSessionStateModel, IControllerSessionStateDocument> {
1129
+ declare static exact: plugins.smartdata.TExact<ControllerSessionStateModel>;
1130
+
1131
+ @plugins.smartdata.unI()
1132
+ public id!: string;
1133
+
1134
+ @plugins.smartdata.svDb()
1135
+ public controllerId!: string;
1136
+
1137
+ @plugins.smartdata.svDb()
1138
+ public projectId!: string;
1139
+
1140
+ @plugins.smartdata.svDb()
1141
+ public sessionId!: IControllerRuntimeId;
1142
+
1143
+ @plugins.smartdata.svDb()
1144
+ public scratchpad!: IControllerSessionStateDocument['scratchpad'];
1145
+
1146
+ @plugins.smartdata.svDb()
1147
+ public intelligenceExchanges!: IControllerSessionStateDocument['intelligenceExchanges'];
1148
+
1149
+ @plugins.smartdata.svDb()
1150
+ public flexProjectManagement?: IControllerSessionStateDocument['flexProjectManagement'];
1151
+
1152
+ @plugins.smartdata.svDb()
1153
+ public modelChoice?: TControllerModelChoice;
1154
+
1155
+ @plugins.smartdata.svDb()
1156
+ public updateId!: string;
1157
+
1158
+ @plugins.smartdata.index({ expireAfterSeconds: 30 * 24 * 60 * 60 })
1159
+ public deletedAt?: Date;
1160
+ }
1161
+
1162
+ export const assertControllerTempPasswordDocument = (
1163
+ valueArg: unknown,
1164
+ ): asserts valueArg is IControllerTempPasswordDocument => {
1165
+ if (!isPlainObject(valueArg)) throw new Error('Invalid controller temp password document.');
1166
+ if (
1167
+ !hasExactKeys(valueArg, ['id', 'controllerId', 'secretHash', 'createdAt', 'expiresAt'])
1168
+ || !isNonEmptyString(valueArg.id)
1169
+ || valueArg.id.length > 512
1170
+ || !isNonEmptyString(valueArg.controllerId)
1171
+ || !/^controller:[1-9][0-9]{0,4}$/.test(valueArg.controllerId)
1172
+ || !isBase64Url(valueArg.secretHash, 32)
1173
+ || !isValidDate(valueArg.createdAt)
1174
+ || !isValidDate(valueArg.expiresAt)
1175
+ || valueArg.expiresAt.getTime() <= valueArg.createdAt.getTime()
1176
+ ) {
1177
+ throw new Error('Invalid controller temp password document.');
1178
+ }
1179
+ };
1180
+
1181
+ @plugins.smartdata.managed({ collectionName: 'opencode_controller_temp_passwords' })
1182
+ @plugins.smartdata.exactPersistence({
1183
+ assertDocument: assertControllerTempPasswordDocument,
1184
+ })
1185
+ export class ControllerTempPasswordModel
1186
+ extends plugins.smartdata.SmartDataDbDoc<
1187
+ ControllerTempPasswordModel,
1188
+ IControllerTempPasswordDocument
1189
+ > {
1190
+ declare static exact: plugins.smartdata.TExact<ControllerTempPasswordModel>;
1191
+
1192
+ @plugins.smartdata.unI()
1193
+ public id!: string;
1194
+
1195
+ @plugins.smartdata.svDb()
1196
+ public controllerId!: string;
1197
+
1198
+ @plugins.smartdata.svDb()
1199
+ public secretHash!: string;
1200
+
1201
+ @plugins.smartdata.svDb()
1202
+ public createdAt!: Date;
1203
+
1204
+ @plugins.smartdata.index({ expireAfterSeconds: 0 })
1205
+ public expiresAt!: Date;
1206
+ }
1207
+
1208
+ @plugins.smartdata.managed({ collectionName: 'opencode_controller_audit' })
1209
+ @plugins.smartdata.exactPersistence({
1210
+ assertDocument: assertControllerAuditEventDocument,
1211
+ })
1212
+ export class ControllerAuditEventModel
1213
+ extends plugins.smartdata.SmartDataDbDoc<ControllerAuditEventModel, IControllerAuditEvent> {
1214
+ declare static exact: plugins.smartdata.TExact<ControllerAuditEventModel>;
1215
+
1216
+ @plugins.smartdata.unI()
1217
+ public id!: string;
1218
+
1219
+ @plugins.smartdata.svDb()
1220
+ public controllerId!: string;
1221
+
1222
+ @plugins.smartdata.index({ expireAfterSeconds: auditRetentionSeconds })
1223
+ public timestamp!: Date;
1224
+
1225
+ @plugins.smartdata.svDb()
1226
+ public type!: IControllerAuditEvent['type'];
1227
+
1228
+ @plugins.smartdata.svDb()
1229
+ public outcome!: IControllerAuditEvent['outcome'];
1230
+
1231
+ @plugins.smartdata.svDb()
1232
+ public operationId?: string;
1233
+
1234
+ @plugins.smartdata.svDb()
1235
+ public peerId?: string;
1236
+
1237
+ @plugins.smartdata.svDb()
1238
+ public credentialId?: string;
1239
+
1240
+ @plugins.smartdata.svDb()
1241
+ public sessionId?: IControllerRuntimeId;
1242
+
1243
+ @plugins.smartdata.svDb()
1244
+ public requestId?: IControllerRuntimeId;
1245
+ }