import * as plugins from './plugins.js'; import { assertControllerFlexCleanupCohort } from './classes.managedsessionmodels.js'; import { isControllerTerminalAgentSessionId } from './functions.terminalagents.js'; import { controllerAuditEventTypes, type IControllerAuditEvent, type IControllerAuthDocument, type IControllerTempPasswordDocument, type ISetupAuthorityState, type IStoredPasskeyCredential, type IWebAuthnCeremonyDocument, type TControllerAuditSessionId, } from './interfaces.auth.js'; import type { IControllerRuntimeConfig } from './interfaces.config.js'; import { controllerFilesystemIdentityMaximumComponents, type IControllerFilesystemIdentity, type IControllerProjectDocument, type IControllerResourceDocument, type IControllerSessionStateDocument, type IControllerSessionGroupsDocument, type IControllerSettingsDocument, type IControllerTrackedConversationDocument, } from './interfaces.projects.js'; import { controllerLayoutItemRefKey, controllerRuntimeIdKey, type TControllerModelChoice, type IControllerRuntimeId, type IControllerSessionGroup, type TControllerLayoutItemRef, type TControllerSessionId, type TControllerHarnessId, type TControllerSessionHarnessId, controllerProjectRemovalBlockedCodes, controllerProjectRemovalBlockedReasonMaxLength, type TControllerProjectRemovalBlockedCode, controllerSessionHarnessIds, controllerSessionLayoutGroupLimit, controllerSessionLayoutUngroupedLimit, controllerStandardProjectDirectoryLimit, } from '../ts_interfaces/index.js'; const base64UrlPattern = /^[A-Za-z0-9_-]+$/; const credentialTransports = new Set([ 'ble', 'cable', 'hybrid', 'internal', 'nfc', 'smart-card', 'usb', ]); const auditEventTypes: ReadonlySet = new Set(controllerAuditEventTypes); const auditOutcomes = new Set(['attempted', 'succeeded', 'denied', 'failed']); const auditRetentionSeconds = 90 * 24 * 60 * 60; const isPlainObject = (valueArg: unknown): valueArg is Record => typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg) && ( Object.getPrototypeOf(valueArg) === Object.prototype || Object.getPrototypeOf(valueArg) === null ); const hasExactKeys = ( valueArg: Record, requiredKeysArg: readonly string[], optionalKeysArg: readonly string[] = [], ): boolean => { const keys = Object.keys(valueArg); return requiredKeysArg.every((key) => Object.hasOwn(valueArg, key)) && keys.every((key) => requiredKeysArg.includes(key) || optionalKeysArg.includes(key)); }; const isNonEmptyString = (valueArg: unknown): valueArg is string => typeof valueArg === 'string' && valueArg.length > 0; const isRuntimeId = ( valueArg: unknown, allowedHarnessIdsArg: readonly TControllerHarnessId[], ): valueArg is IControllerRuntimeId => isPlainObject(valueArg) && hasExactKeys(valueArg, ['harnessId', 'nativeId']) && allowedHarnessIdsArg.includes(valueArg.harnessId as TControllerHarnessId) && isNonEmptyString(valueArg.nativeId) && Buffer.byteLength(valueArg.nativeId, 'utf8') <= 512; /** * A Claude Code conversation as BrowserRuntime qualifies it. It appears in exactly one place — * the acting session of an audited browser operation driven by the conversation that owns an * attached terminal — because AGL does not manage such a conversation and has no runtime id for * it. It is deliberately not a `TControllerHarnessId`. */ const isClaudeSessionId = (valueArg: unknown): boolean => isPlainObject(valueArg) && hasExactKeys(valueArg, ['harnessId', 'nativeId']) && valueArg.harnessId === 'claude' && isNonEmptyString(valueArg.nativeId) && Buffer.byteLength(valueArg.nativeId, 'utf8') <= 512; const isBase64Url = (valueArg: unknown, byteLengthArg?: number): valueArg is string => { if (!isNonEmptyString(valueArg) || !base64UrlPattern.test(valueArg)) return false; if (byteLengthArg === undefined) return true; try { return Buffer.from(valueArg, 'base64url').byteLength === byteLengthArg; } catch { return false; } }; const isValidDate = (valueArg: unknown): valueArg is Date => valueArg instanceof Date && Number.isFinite(valueArg.getTime()); const isValidPort = (valueArg: unknown): valueArg is number => Number.isSafeInteger(valueArg) && (valueArg as number) >= 1 && (valueArg as number) <= 65535; const isRuntimeConfig = (valueArg: unknown): valueArg is IControllerRuntimeConfig => { if (!isPlainObject(valueArg)) return false; if ( !hasExactKeys(valueArg, [ 'controllerPort', 'publicOrigin', 'rpId', 'projectsRoot', 'opencodePort', 'tlsMode', ]) || !isNonEmptyString(valueArg.projectsRoot) || !plugins.path.isAbsolute(valueArg.projectsRoot) || plugins.path.normalize(valueArg.projectsRoot) !== valueArg.projectsRoot || !isValidPort(valueArg.controllerPort) || !isValidPort(valueArg.opencodePort) || valueArg.controllerPort === valueArg.opencodePort || !isNonEmptyString(valueArg.publicOrigin) || !isNonEmptyString(valueArg.rpId) || !['local', 'proxy'].includes(valueArg.tlsMode as string) ) { return false; } try { const origin = new URL(valueArg.publicOrigin); const hostname = origin.hostname.toLowerCase().replace(/\.$/, ''); const rpId = valueArg.rpId.toLowerCase().replace(/\.$/, ''); if ( origin.origin !== valueArg.publicOrigin || (origin.protocol !== 'http:' && origin.protocol !== 'https:') || origin.username.length > 0 || origin.password.length > 0 || origin.pathname !== '/' || origin.search.length > 0 || origin.hash.length > 0 || valueArg.rpId !== rpId || rpId.length === 0 || rpId.includes('/') || rpId.includes(':') || /\s/.test(rpId) || (hostname !== rpId && !hostname.endsWith(`.${rpId}`)) ) { return false; } if (valueArg.tlsMode === 'local') { return origin.protocol === 'http:' && hostname === 'localhost'; } return origin.protocol === 'https:'; } catch { return false; } }; const isSetupAuthorityState = (valueArg: unknown): valueArg is ISetupAuthorityState => { if (!isPlainObject(valueArg)) return false; return hasExactKeys(valueArg, ['generation', 'hash', 'expiresAt']) && isBase64Url(valueArg.generation, 32) && isBase64Url(valueArg.hash, 32) && isValidDate(valueArg.expiresAt); }; const isStoredCredential = (valueArg: unknown): valueArg is IStoredPasskeyCredential => { if (!isPlainObject(valueArg)) return false; return hasExactKeys(valueArg, [ 'id', 'publicKey', 'counter', 'transports', 'deviceType', 'backedUp', 'createdAt', 'counterUpdateId', ]) && isBase64Url(valueArg.id) && isBase64Url(valueArg.publicKey) && Number.isSafeInteger(valueArg.counter) && (valueArg.counter as number) >= 0 && Array.isArray(valueArg.transports) && valueArg.transports.every((entry) => credentialTransports.has(entry as string)) && (valueArg.deviceType === 'singleDevice' || valueArg.deviceType === 'multiDevice') && typeof valueArg.backedUp === 'boolean' && isValidDate(valueArg.createdAt) && isBase64Url(valueArg.counterUpdateId, 32); }; export const assertControllerAuthDocument = ( valueArg: unknown, ): asserts valueArg is IControllerAuthDocument => { if (!isPlainObject(valueArg)) throw new Error('Invalid controller authentication document.'); if ( !hasExactKeys(valueArg, [ 'id', 'config', 'authState', 'setup', 'webauthnUserId', 'credentials', ]) || !isRuntimeConfig(valueArg.config) || valueArg.id !== `controller:${valueArg.config.controllerPort}` || !isBase64Url(valueArg.webauthnUserId, 32) || !Array.isArray(valueArg.credentials) || !valueArg.credentials.every(isStoredCredential) || new Set(valueArg.credentials.map((credential) => credential.id)).size !== valueArg.credentials.length ) { throw new Error('Invalid controller authentication document.'); } if (valueArg.credentials.length === 0) { if ( valueArg.authState !== 'setupRequired' || (valueArg.setup !== null && !isSetupAuthorityState(valueArg.setup)) ) { throw new Error('Invalid controller setup state.'); } return; } if (valueArg.authState !== 'ready' || valueArg.setup !== null) { throw new Error('Invalid enrolled controller authentication state.'); } }; const assertCeremonyStateFields = (valueArg: Record): boolean => { if (valueArg.state === 'pending') { return !Object.hasOwn(valueArg, 'consumedAt') && !Object.hasOwn(valueArg, 'consumedNonce'); } return valueArg.state === 'consumed' && isValidDate(valueArg.consumedAt) && isBase64Url(valueArg.consumedNonce, 32); }; export const assertWebAuthnCeremonyDocument = ( valueArg: unknown, ): asserts valueArg is IWebAuthnCeremonyDocument => { if (!isPlainObject(valueArg)) throw new Error('Invalid WebAuthn ceremony document.'); const optionalKeys = valueArg.kind === 'setup' ? ['consumedAt', 'consumedNonce'] : ['consumedAt', 'consumedNonce']; const requiredKeys = [ 'id', 'kind', 'state', 'peerId', 'challenge', 'origin', 'rpId', 'userId', 'createdAt', 'expiresAt', ...(valueArg.kind === 'setup' ? ['setupGeneration', 'setupHash'] : []), ]; if ( !hasExactKeys(valueArg, requiredKeys, optionalKeys) || !isBase64Url(valueArg.id, 32) || (valueArg.kind !== 'setup' && valueArg.kind !== 'authentication') || !isNonEmptyString(valueArg.peerId) || valueArg.peerId.length > 512 || !isBase64Url(valueArg.challenge) || !isNonEmptyString(valueArg.origin) || !isNonEmptyString(valueArg.rpId) || !isBase64Url(valueArg.userId, 32) || !isValidDate(valueArg.createdAt) || !isValidDate(valueArg.expiresAt) || valueArg.expiresAt.getTime() <= valueArg.createdAt.getTime() || !assertCeremonyStateFields(valueArg) ) { throw new Error('Invalid WebAuthn ceremony document.'); } let origin: URL; try { origin = new URL(valueArg.origin); } catch { throw new Error('Invalid WebAuthn ceremony origin.'); } const hostname = origin.hostname.toLowerCase().replace(/\.$/, ''); if ( origin.origin !== valueArg.origin || (origin.protocol !== 'http:' && origin.protocol !== 'https:') || (hostname !== valueArg.rpId && !hostname.endsWith(`.${valueArg.rpId}`)) ) { throw new Error('Invalid WebAuthn ceremony origin or RP ID.'); } if ( valueArg.kind === 'setup' && (!isBase64Url(valueArg.setupGeneration, 32) || !isBase64Url(valueArg.setupHash, 32)) ) { throw new Error('Invalid WebAuthn setup ceremony authority.'); } }; export const assertControllerAuditEventDocument = ( valueArg: unknown, ): asserts valueArg is IControllerAuditEvent => { if (!isPlainObject(valueArg)) throw new Error('Invalid controller audit event.'); if ( !hasExactKeys( valueArg, ['id', 'controllerId', 'timestamp', 'type', 'outcome'], ['operationId', 'peerId', 'credentialId', 'sessionId', 'requestId'], ) || !isBase64Url(valueArg.id, 32) || !isNonEmptyString(valueArg.controllerId) || !/^controller:[1-9][0-9]{0,4}$/.test(valueArg.controllerId) || Number(valueArg.controllerId.slice('controller:'.length)) > 65535 || !isValidDate(valueArg.timestamp) || !auditEventTypes.has(valueArg.type as string) || !auditOutcomes.has(valueArg.outcome as string) || ( Object.hasOwn(valueArg, 'operationId') && !isBase64Url(valueArg.operationId, valueArg.type === 'resource.browser.operation' ? 18 : 24) ) ) { throw new Error('Invalid controller audit event.'); } for (const optionalKey of [ 'peerId', 'credentialId', 'sessionId', 'requestId', ] as const) { if ( Object.hasOwn(valueArg, optionalKey) && !( // Protocol v5 audit rows are retained until TTL expiry; all new writes // are qualified by IRecordAuditEventInput. (isNonEmptyString(valueArg[optionalKey]) && valueArg[optionalKey].length <= 512) || isRuntimeId(valueArg[optionalKey], ['opencode', 'flex', 'codex', 'controller']) || (optionalKey === 'sessionId' && isClaudeSessionId(valueArg[optionalKey])) ) ) { throw new Error('Invalid controller audit event identifier.'); } } }; // A project name is a display path: relative to the projects root for // directories inside it, else the absolute directory path. Absolute names may // contain hidden (dot-prefixed) segments because they were typed explicitly; // relative names stay restricted to visible directory names. export const isProjectRelativePathName = (valueArg: unknown): valueArg is string => { if (typeof valueArg !== 'string' || valueArg.length < 1 || valueArg.length > 4096) { return false; } const absolute = valueArg.startsWith('/'); const segments = absolute ? valueArg.slice(1).split('/') : valueArg.split('/'); return segments.every((segment) => ( segment.length > 0 && segment.length <= 255 && segment !== '.' && segment !== '..' && (absolute || !segment.startsWith('.')) // eslint-disable-next-line no-control-regex && !/[\x00-\x1f\x7f\\]/.test(segment) )); }; const controllerIdPattern = /^controller:[1-9][0-9]{0,4}$/; const isControllerId = (valueArg: unknown): valueArg is string => isNonEmptyString(valueArg) && controllerIdPattern.test(valueArg) && Number(valueArg.slice('controller:'.length)) <= 65535; const isFilesystemIdentityPart = (valueArg: unknown): valueArg is string => ( typeof valueArg === 'string' && /^(?:0|[1-9][0-9]{0,31})$/u.test(valueArg) ); const isFilesystemIdentity = ( valueArg: unknown, pathArg: string, targetTypeArg: 'directory' | 'file', ): valueArg is IControllerFilesystemIdentity => { if (!isPlainObject(valueArg) || !hasExactKeys(valueArg, ['ancestry'])) return false; if ( !Array.isArray(valueArg.ancestry) || valueArg.ancestry.length < 1 || valueArg.ancestry.length > controllerFilesystemIdentityMaximumComponents ) return false; const root = plugins.path.parse(pathArg).root; const relative = pathArg.slice(root.length); const expectedLength = 1 + relative.split(plugins.path.sep).filter(Boolean).length; const ancestry = valueArg.ancestry; if (ancestry.length !== expectedLength) return false; return ancestry.every((component, index) => ( isPlainObject(component) && hasExactKeys(component, ['device', 'inode', 'type']) && isFilesystemIdentityPart(component.device) && isFilesystemIdentityPart(component.inode) && component.type === (index === ancestry.length - 1 ? targetTypeArg : 'directory') )); }; export function assertControllerProjectDocument( valueArg: unknown, ): asserts valueArg is IControllerProjectDocument { if (!isPlainObject(valueArg)) throw new Error('Invalid controller project document.'); if ( !hasExactKeys( valueArg, ['id', 'controllerId', 'name', 'directory', 'directoryIdentityState', 'createdAt'], [ 'directoryIdentity', 'removalStartedAt', 'removalOperationId', 'flexCleanupCohort', 'removalBlockedAt', 'removalBlockedCode', 'removalBlockedReason', 'removedAt', ], ) || !isBase64Url(valueArg.id, 16) || !isControllerId(valueArg.controllerId) || !isProjectRelativePathName(valueArg.name) || !isNonEmptyString(valueArg.directory) || !plugins.path.isAbsolute(valueArg.directory) || Buffer.byteLength(valueArg.directory, 'utf8') > 4096 || ( valueArg.directoryIdentityState !== 'bound' && valueArg.directoryIdentityState !== 'unbound' ) || ( valueArg.directoryIdentityState === 'bound' && !isFilesystemIdentity(valueArg.directoryIdentity, valueArg.directory, 'directory') ) || ( valueArg.directoryIdentityState === 'unbound' && Object.hasOwn(valueArg, 'directoryIdentity') ) || !isValidDate(valueArg.createdAt) || (Object.hasOwn(valueArg, 'removalStartedAt') && !isValidDate(valueArg.removalStartedAt)) || ( Object.hasOwn(valueArg, 'removalOperationId') && !isBase64Url(valueArg.removalOperationId, 24) ) || (Object.hasOwn(valueArg, 'removedAt') && !isValidDate(valueArg.removedAt)) || ( Object.hasOwn(valueArg, 'removalStartedAt') && Object.hasOwn(valueArg, 'removedAt') ) ) { throw new Error('Invalid controller project document.'); } const hasRemovalStartedAt = Object.hasOwn(valueArg, 'removalStartedAt'); const hasRemovedAt = Object.hasOwn(valueArg, 'removedAt'); const hasRemovalOperationId = Object.hasOwn(valueArg, 'removalOperationId'); const hasFlexCleanupCohort = Object.hasOwn(valueArg, 'flexCleanupCohort'); if ( (hasRemovalStartedAt && hasRemovalOperationId !== hasFlexCleanupCohort) || (hasRemovedAt && hasFlexCleanupCohort) || (!hasRemovalStartedAt && !hasRemovedAt && (hasRemovalOperationId || hasFlexCleanupCohort)) ) throw new Error('Invalid controller project removal authority.'); const blockedKeys = ['removalBlockedAt', 'removalBlockedCode', 'removalBlockedReason'] as const; const blockedPresent = blockedKeys.filter((key) => Object.hasOwn(valueArg, key)); if ( (blockedPresent.length !== 0 && blockedPresent.length !== blockedKeys.length) || (blockedPresent.length > 0 && (!hasRemovalStartedAt || hasRemovedAt)) ) throw new Error('Invalid controller project removal block.'); if ( blockedPresent.length > 0 && ( !isValidDate(valueArg.removalBlockedAt) || !controllerProjectRemovalBlockedCodes.includes( valueArg.removalBlockedCode as TControllerProjectRemovalBlockedCode, ) || !isNonEmptyString(valueArg.removalBlockedReason) || valueArg.removalBlockedReason.length > controllerProjectRemovalBlockedReasonMaxLength ) ) throw new Error('Invalid controller project removal block.'); if (hasFlexCleanupCohort) { assertControllerFlexCleanupCohort(valueArg.flexCleanupCohort); if ( valueArg.flexCleanupCohort.length > 0 && !valueArg.flexCleanupCohort.some((entry) => entry.cleanupRoot) ) throw new Error('Invalid controller project Flex cleanup roots.'); } } const isModelChoice = (valueArg: unknown): valueArg is TControllerModelChoice => { if (!isPlainObject(valueArg)) return false; const hasValidVariant = !Object.hasOwn(valueArg, 'variant') || ( isNonEmptyString(valueArg.variant) && valueArg.variant.length <= 64 && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(valueArg.variant) ); if ( !hasValidVariant || !isNonEmptyString(valueArg.providerID) || valueArg.providerID.length > 256 || !isNonEmptyString(valueArg.modelID) || valueArg.modelID.length > 256 ) { return false; } if (valueArg.harnessId === 'opencode') { return hasExactKeys(valueArg, ['harnessId', 'providerID', 'modelID'], ['variant']); } return (valueArg.harnessId === 'flex' || (valueArg.harnessId === 'codex' && valueArg.providerID === 'codex')) && hasExactKeys(valueArg, ['harnessId', 'providerID', 'modelID'], ['variant']); }; /** * Absolute, normalized, duplicate-free directories. Existence is a controller-side check * because the filesystem is not part of the document. */ export const isStandardProjectDirectoryList = ( valueArg: unknown, ): valueArg is string[] => { if (!Array.isArray(valueArg) || valueArg.length > controllerStandardProjectDirectoryLimit) { return false; } const seen = new Set(); for (const entry of valueArg) { if ( !isNonEmptyString(entry) || Buffer.byteLength(entry, 'utf8') > 4096 || !plugins.path.isAbsolute(entry) || plugins.path.normalize(entry) !== entry || (entry.length > 1 && entry.endsWith(plugins.path.sep)) // eslint-disable-next-line no-control-regex || /[\x00-\x1f\x7f]/.test(entry) || seen.has(entry) ) return false; seen.add(entry); } return true; }; export function assertControllerSettingsDocument( valueArg: unknown, ): asserts valueArg is IControllerSettingsDocument { if (!isPlainObject(valueArg)) throw new Error('Invalid controller settings document.'); if ( !hasExactKeys( valueArg, ['id', 'controllerId', 'defaultModels'], [ 'autoAcceptPermissions', 'selectedOpenCodeProviderConnectionId', 'browserVideoBackend', 'standardProjectDirectories', 'lastSessionHarnessId', ], ) || !isControllerId(valueArg.controllerId) || valueArg.id !== `${valueArg.controllerId}:settings` || !Array.isArray(valueArg.defaultModels) || valueArg.defaultModels.length > 3 || !valueArg.defaultModels.every(isModelChoice) || new Set(valueArg.defaultModels.map( (model) => (model as TControllerModelChoice).harnessId, )).size !== valueArg.defaultModels.length // Disabled is represented by an absent key, never by a stored false. || (Object.hasOwn(valueArg, 'autoAcceptPermissions') && valueArg.autoAcceptPermissions !== true) || (Object.hasOwn(valueArg, 'browserVideoBackend') && valueArg.browserVideoBackend !== 'native') || (valueArg.selectedOpenCodeProviderConnectionId !== undefined && ( !isNonEmptyString(valueArg.selectedOpenCodeProviderConnectionId) || Buffer.byteLength(valueArg.selectedOpenCodeProviderConnectionId, 'utf8') > 512 )) || ( Object.hasOwn(valueArg, 'standardProjectDirectories') && !isStandardProjectDirectoryList(valueArg.standardProjectDirectories) ) || ( Object.hasOwn(valueArg, 'lastSessionHarnessId') && !controllerSessionHarnessIds.includes( valueArg.lastSessionHarnessId as TControllerSessionHarnessId, ) ) ) { throw new Error('Invalid controller settings document.'); } } @plugins.smartdata.managed({ collectionName: 'opencode_controller_auth' }) @plugins.smartdata.exactPersistence({ assertDocument: assertControllerAuthDocument, }) export class ControllerAuthModel extends plugins.smartdata.SmartDataDbDoc { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public config!: IControllerRuntimeConfig; @plugins.smartdata.svDb() public authState!: IControllerAuthDocument['authState']; @plugins.smartdata.svDb() public setup!: ISetupAuthorityState | null; @plugins.smartdata.svDb() public webauthnUserId!: string; @plugins.smartdata.svDb() public credentials!: IStoredPasskeyCredential[]; } @plugins.smartdata.managed({ collectionName: 'opencode_controller_webauthn_ceremonies' }) @plugins.smartdata.exactPersistence({ assertDocument: assertWebAuthnCeremonyDocument, }) export class WebAuthnCeremonyModel extends plugins.smartdata.SmartDataDbDoc { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public kind!: IWebAuthnCeremonyDocument['kind']; @plugins.smartdata.svDb() public state!: IWebAuthnCeremonyDocument['state']; @plugins.smartdata.svDb() public peerId!: string; @plugins.smartdata.svDb() public challenge!: string; @plugins.smartdata.svDb() public origin!: string; @plugins.smartdata.svDb() public rpId!: string; @plugins.smartdata.svDb() public userId!: string; @plugins.smartdata.svDb() public setupGeneration?: string; @plugins.smartdata.svDb() public setupHash?: string; @plugins.smartdata.svDb() public createdAt!: Date; @plugins.smartdata.index({ expireAfterSeconds: 0 }) public expiresAt!: Date; @plugins.smartdata.svDb() public consumedAt?: Date; @plugins.smartdata.svDb() public consumedNonce?: string; } @plugins.smartdata.managed({ collectionName: 'opencode_controller_projects' }) @plugins.smartdata.exactPersistence({ assertDocument: assertControllerProjectDocument, }) export class ControllerProjectModel extends plugins.smartdata.SmartDataDbDoc { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public controllerId!: string; // `name` shadows a base-class property; the initializer makes that explicit. @plugins.smartdata.svDb() public name: string = ''; @plugins.smartdata.svDb() public directory!: string; @plugins.smartdata.svDb() public directoryIdentityState!: IControllerProjectDocument['directoryIdentityState']; @plugins.smartdata.svDb() public directoryIdentity?: IControllerProjectDocument['directoryIdentity']; @plugins.smartdata.svDb() public createdAt!: Date; @plugins.smartdata.svDb() public removalStartedAt?: Date; @plugins.smartdata.svDb() public removalOperationId?: string; @plugins.smartdata.svDb() public flexCleanupCohort?: IControllerProjectDocument['flexCleanupCohort']; @plugins.smartdata.svDb() public removalBlockedAt?: Date; @plugins.smartdata.svDb() public removalBlockedCode?: IControllerProjectDocument['removalBlockedCode']; @plugins.smartdata.svDb() public removalBlockedReason?: string; // Deregistered projects expire from the collection after 90 days; the // directory on disk is never touched. @plugins.smartdata.index({ expireAfterSeconds: 90 * 24 * 60 * 60 }) public removedAt?: Date; } @plugins.smartdata.managed({ collectionName: 'opencode_controller_settings' }) @plugins.smartdata.exactPersistence({ assertDocument: assertControllerSettingsDocument, }) export class ControllerSettingsModel extends plugins.smartdata.SmartDataDbDoc { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public controllerId!: string; @plugins.smartdata.svDb() public defaultModels!: TControllerModelChoice[]; @plugins.smartdata.svDb() public autoAcceptPermissions?: true; @plugins.smartdata.svDb() public browserVideoBackend?: 'native'; @plugins.smartdata.svDb() public selectedOpenCodeProviderConnectionId?: string; @plugins.smartdata.svDb() public standardProjectDirectories?: string[]; @plugins.smartdata.svDb() public lastSessionHarnessId?: TControllerSessionHarnessId; } export const controllerTrackedConversationId = ( controllerIdArg: string, projectIdArg: string, sessionIdArg: TControllerSessionId, ): string => `trackedconversation:v1:${plugins.crypto .createHash('sha256') .update(JSON.stringify([ controllerIdArg, projectIdArg, sessionIdArg.harnessId, sessionIdArg.nativeId, ])) .digest('base64url')}`; const maxTrackedConversationTitleBytes = 1024; export function assertControllerTrackedConversationDocument( valueArg: unknown, ): asserts valueArg is IControllerTrackedConversationDocument { if (!isPlainObject(valueArg)) throw new Error('Invalid controller tracked conversation document.'); const hasTitleCache = Object.hasOwn(valueArg, 'titleCache'); if ( !hasExactKeys( valueArg, [ 'id', 'controllerId', 'projectId', 'harnessId', 'runtimeId', 'sessionIdentityId', 'origin', 'trackedAt', 'updateId', ], ['archivedAt', 'titleCache', 'titleCacheAt'], ) || !isControllerId(valueArg.controllerId) || !isNonEmptyString(valueArg.projectId) || Buffer.byteLength(valueArg.projectId, 'utf8') > 64 || !isRuntimeId(valueArg.runtimeId, ['opencode', 'flex', 'codex']) || valueArg.harnessId !== valueArg.runtimeId.harnessId || valueArg.id !== controllerTrackedConversationId( valueArg.controllerId, valueArg.projectId, valueArg.runtimeId as TControllerSessionId, ) || !isBase64Url(valueArg.sessionIdentityId, 32) || (valueArg.origin !== 'created' && valueArg.origin !== 'opened') || !isValidDate(valueArg.trackedAt) || (Object.hasOwn(valueArg, 'archivedAt') && !isValidDate(valueArg.archivedAt)) || (hasTitleCache && ( typeof valueArg.titleCache !== 'string' || Buffer.byteLength(valueArg.titleCache, 'utf8') > maxTrackedConversationTitleBytes // eslint-disable-next-line no-control-regex || /[\x00-\x1f\x7f]/.test(valueArg.titleCache) )) // The cache timestamp is meaningless without its value and vice versa. || hasTitleCache !== Object.hasOwn(valueArg, 'titleCacheAt') || (Object.hasOwn(valueArg, 'titleCacheAt') && !isValidDate(valueArg.titleCacheAt)) || !isBase64Url(valueArg.updateId, 32) ) { throw new Error('Invalid controller tracked conversation document.'); } } @plugins.smartdata.compoundIndex({ name: 'controller_tracked_conversation_scope', key: { controllerId: 1, projectId: 1, id: 1, }, }) @plugins.smartdata.managed({ collectionName: 'opencode_controller_tracked_conversations' }) @plugins.smartdata.exactPersistence({ assertDocument: assertControllerTrackedConversationDocument, }) export class ControllerTrackedConversationModel extends plugins.smartdata.SmartDataDbDoc< ControllerTrackedConversationModel, IControllerTrackedConversationDocument > { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public controllerId!: string; @plugins.smartdata.svDb() public projectId!: string; @plugins.smartdata.svDb() public harnessId!: IControllerTrackedConversationDocument['harnessId']; @plugins.smartdata.svDb() public runtimeId!: TControllerSessionId; @plugins.smartdata.svDb() public sessionIdentityId!: string; @plugins.smartdata.svDb() public origin!: IControllerTrackedConversationDocument['origin']; @plugins.smartdata.svDb() public trackedAt!: Date; @plugins.smartdata.svDb() public archivedAt?: Date; @plugins.smartdata.svDb() public titleCache?: string; @plugins.smartdata.svDb() public titleCacheAt?: Date; @plugins.smartdata.svDb() public updateId!: string; } // The layout is controller-wide, so the bounds cover every project's items at once and are // shared with the request validation rather than restated here. const maxSessionGroups = controllerSessionLayoutGroupLimit; const maxSessionsPerGroup = 512; const maxUngroupedSessions = controllerSessionLayoutUngroupedLimit; const groupIdentifierPattern = /^[A-Za-z0-9_-]{1,128}$/; const resourceIdPattern = /^[A-Za-z0-9_-]{22}$/; /** * A layout member. Conversations and resources are peers in one ordered list, and their ids live * in separate namespaces, so the kind is carried rather than inferred. */ export const isLayoutItemRef = (valueArg: unknown): valueArg is TControllerLayoutItemRef => { if (!isPlainObject(valueArg)) return false; if (!isNonEmptyString(valueArg.projectId) || !groupIdentifierPattern.test(valueArg.projectId)) { return false; } if (valueArg.kind === 'session') { return hasExactKeys(valueArg, ['kind', 'id', 'projectId']) && isRuntimeId(valueArg.id, ['opencode', 'flex', 'codex']); } if (valueArg.kind === 'resource') { return hasExactKeys(valueArg, ['kind', 'id', 'projectId']) && isNonEmptyString(valueArg.id) && resourceIdPattern.test(valueArg.id); } return false; }; export const isSessionGroupList = ( valueArg: unknown, ): valueArg is IControllerSessionGroupsDocument['groups'] => { if (!Array.isArray(valueArg) || valueArg.length > maxSessionGroups) return false; const groupIds = new Set(); const memberIds = new Set(); for (const group of valueArg) { if ( !isPlainObject(group) || !hasExactKeys(group, ['id', 'name', 'itemIds']) || !isNonEmptyString(group.id) || !groupIdentifierPattern.test(group.id) || groupIds.has(group.id) || !isNonEmptyString(group.name) || group.name.length > 128 // eslint-disable-next-line no-control-regex || /[\x00-\x1f\x7f]/.test(group.name) || !Array.isArray(group.itemIds) || group.itemIds.length > maxSessionsPerGroup ) { return false; } groupIds.add(group.id); for (const itemId of group.itemIds) { if ( !isLayoutItemRef(itemId) // An item belongs to at most one group; duplicates are invalid. || memberIds.has(controllerLayoutItemRefKey(itemId)) ) { return false; } memberIds.add(controllerLayoutItemRefKey(itemId)); } } return true; }; export const isUngroupedItemList = ( valueArg: unknown, groupedItemIdsArg: ReadonlySet = new Set(), ): valueArg is TControllerLayoutItemRef[] => { if (!Array.isArray(valueArg) || valueArg.length > maxUngroupedSessions) return false; const itemIds = new Set(); for (const itemId of valueArg) { const key = isLayoutItemRef(itemId) ? controllerLayoutItemRefKey(itemId) : undefined; if ( key === undefined || groupedItemIdsArg.has(key) || itemIds.has(key) ) { return false; } itemIds.add(key); } return true; }; export function assertControllerSessionGroupsDocument( valueArg: unknown, ): asserts valueArg is IControllerSessionGroupsDocument { if (!isPlainObject(valueArg)) throw new Error('Invalid controller session groups document.'); if ( !hasExactKeys( valueArg, ['id', 'controllerId', 'scope', 'groups'], ['ungroupedItemIds', 'revision'], ) || !isControllerId(valueArg.controllerId) || valueArg.scope !== 'controller' || valueArg.id !== `${valueArg.controllerId}:groups` || !isSessionGroupList(valueArg.groups) ) { throw new Error('Invalid controller session groups document.'); } const groupedItemIds = new Set( valueArg.groups.flatMap((group) => group.itemIds.map(controllerLayoutItemRefKey)), ); const hasUngroupedItemIds = valueArg.ungroupedItemIds !== undefined; const hasRevision = valueArg.revision !== undefined; if ( hasUngroupedItemIds !== hasRevision || ( hasRevision && (!Number.isSafeInteger(valueArg.revision) || (valueArg.revision as number) < 0) ) || hasUngroupedItemIds && !isUngroupedItemList(valueArg.ungroupedItemIds, groupedItemIds) ) { throw new Error('Invalid controller session groups document.'); } } @plugins.smartdata.managed({ collectionName: 'opencode_controller_session_groups' }) @plugins.smartdata.exactPersistence({ assertDocument: assertControllerSessionGroupsDocument, }) export class ControllerSessionGroupsModel extends plugins.smartdata.SmartDataDbDoc { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public controllerId!: string; @plugins.smartdata.svDb() public scope!: IControllerSessionGroupsDocument['scope']; @plugins.smartdata.svDb() public groups!: IControllerSessionGroupsDocument['groups']; @plugins.smartdata.svDb() public ungroupedItemIds?: IControllerSessionGroupsDocument['ungroupedItemIds']; @plugins.smartdata.svDb() public revision?: number; } const resourceAuthorityPattern = /^[A-Za-z0-9_-]{43}$/; const isResourceSessionIdentityId = (valueArg: unknown): valueArg is string => ( isBase64Url(valueArg, 32) && Buffer.from(valueArg, 'base64url').toString('base64url') === valueArg ); /** A terminal attachment subject: an active terminal resource, fenced by its conversation id. */ /** Bounds the set so one resource cannot be attached to an unbounded number of subjects. */ const maxResourceAttachments = 64; /** * One attachment-set entry, validated against the resource that carries it. * * Cycle-freedom is structural rather than checked: only a browser may carry a terminal entry, a * terminal resource may carry session entries only, and no entry may name its own resource. A set * of such entries therefore cannot close a loop. */ const isResourceAttachmentEntry = ( valueArg: unknown, ownerKindArg: unknown, ownerIdArg: unknown, ownerProjectIdArg: unknown, ): boolean => { if (!isPlainObject(valueArg)) return false; if ( !isNonEmptyString(valueArg.projectId) || !groupIdentifierPattern.test(valueArg.projectId) || valueArg.projectId !== ownerProjectIdArg || !isValidDate(valueArg.attachedAt) ) return false; if (valueArg.kind === 'session') { return hasExactKeys(valueArg, ['kind', 'id', 'projectId', 'attachedAt'], ['sessionIdentityId']) && isRuntimeId(valueArg.id, ['opencode', 'flex', 'codex']) && (valueArg.sessionIdentityId === undefined || isResourceSessionIdentityId(valueArg.sessionIdentityId)); } if (valueArg.kind === 'terminal') { return ownerKindArg === 'browser' && hasExactKeys(valueArg, ['kind', 'id', 'projectId', 'attachedAt'], ['agentSessionId']) && typeof valueArg.id === 'string' && resourceIdPattern.test(valueArg.id) && valueArg.id !== ownerIdArg && (valueArg.agentSessionId === undefined || isControllerTerminalAgentSessionId(valueArg.agentSessionId)); } return false; }; const resourceAttachmentEntryKey = (valueArg: Record): string => ( valueArg.kind === 'session' ? `session:${controllerRuntimeIdKey(valueArg.id as IControllerRuntimeId)}` : `terminal:${String(valueArg.id)}` ); const isResourceAttachmentSet = ( valueArg: unknown, ownerKindArg: unknown, ownerIdArg: unknown, ownerProjectIdArg: unknown, ): boolean => { if (!Array.isArray(valueArg) || valueArg.length > maxResourceAttachments) return false; const keys = new Set(); for (const entry of valueArg) { if (!isResourceAttachmentEntry(entry, ownerKindArg, ownerIdArg, ownerProjectIdArg)) return false; const key = resourceAttachmentEntryKey(entry as Record); if (keys.has(key)) return false; keys.add(key); } return true; }; export function assertControllerResourceDocument( valueArg: unknown, ): asserts valueArg is IControllerResourceDocument { if (!isPlainObject(valueArg)) throw new Error('Invalid controller resource document.'); if ( !hasExactKeys( valueArg, [ 'id', 'controllerId', 'projectId', 'kind', 'title', 'attachmentAuthorityId', 'attachmentRevision', 'attachments', 'lifecycle', 'createdAt', 'updatedAt', 'updateId', ], [ 'pendingAttachment', 'terminal', 'retiringAt', 'retiredAt', ], ) || !resourceIdPattern.test(valueArg.id as string) || !isControllerId(valueArg.controllerId) || !isNonEmptyString(valueArg.projectId) || !groupIdentifierPattern.test(valueArg.projectId) || (valueArg.kind !== 'terminal' && valueArg.kind !== 'browser') || !isNonEmptyString(valueArg.title) || Buffer.byteLength(valueArg.title, 'utf8') > 256 // eslint-disable-next-line no-control-regex || /[\x00-\x1f\x7f]/.test(valueArg.title as string) || !resourceAuthorityPattern.test(valueArg.attachmentAuthorityId as string) || !Number.isSafeInteger(valueArg.attachmentRevision) || (valueArg.attachmentRevision as number) < 0 || !isResourceAttachmentSet( valueArg.attachments, valueArg.kind, valueArg.id, valueArg.projectId, ) || !['active', 'retiring', 'retired'].includes(valueArg.lifecycle as string) || !isValidDate(valueArg.createdAt) || !isValidDate(valueArg.updatedAt) || valueArg.updatedAt.getTime() < valueArg.createdAt.getTime() || !isBase64Url(valueArg.updateId, 32) ) { throw new Error('Invalid controller resource document.'); } if (valueArg.pendingAttachment !== undefined) { const pending = valueArg.pendingAttachment; if ( !isPlainObject(pending) || !hasExactKeys( pending, ['operationId', 'attachmentRevision', 'op', 'requestedAt'], ['entry', 'replaces'], ) || !isBase64Url(pending.operationId, 16) || pending.attachmentRevision !== (valueArg.attachmentRevision as number) + 1 || !['add', 'remove', 'replace'].includes(pending.op as string) || (pending.entry !== undefined && !isResourceAttachmentEntry( pending.entry, valueArg.kind, valueArg.id, valueArg.projectId, )) // `add` and `replace` always name the entry they install; only a clearing remove may omit it. || (pending.op !== 'remove' && pending.entry === undefined) || (pending.replaces !== undefined && pending.op !== 'replace') || (pending.op === 'replace' && pending.replaces === undefined) || (pending.replaces !== undefined && !isResourceAttachmentEntry( pending.replaces, valueArg.kind, valueArg.id, valueArg.projectId, )) || !isValidDate(pending.requestedAt) || valueArg.lifecycle !== 'active' ) { throw new Error('Invalid pending controller resource attachment.'); } } if (valueArg.kind === 'terminal') { if (!isPlainObject(valueArg.terminal) || !hasExactKeys( valueArg.terminal, ['command', 'args', 'cwd'], ['stoppedAt', 'lastExitCode', 'agent'], ) || !isNonEmptyString(valueArg.terminal.command) || !plugins.path.isAbsolute(valueArg.terminal.command) || !Array.isArray(valueArg.terminal.args) || valueArg.terminal.args.length > 32 || valueArg.terminal.args.some((arg) => ( typeof arg !== 'string' || Buffer.byteLength(arg, 'utf8') > 4096 )) || !isNonEmptyString(valueArg.terminal.cwd) || !plugins.path.isAbsolute(valueArg.terminal.cwd) || (valueArg.terminal.stoppedAt !== undefined && !isValidDate(valueArg.terminal.stoppedAt)) || (valueArg.terminal.lastExitCode !== undefined && (!Number.isSafeInteger(valueArg.terminal.lastExitCode) || Math.abs(valueArg.terminal.lastExitCode as number) > 255))) { throw new Error('Invalid controller terminal resource metadata.'); } if (valueArg.terminal.agent !== undefined) { const agent = valueArg.terminal.agent; if ( !isPlainObject(agent) || !hasExactKeys( agent, ['kind', 'sessionId', 'desiredState', 'consecutiveFailures'], ['launchMode', 'lastFailure', 'lastFailureMessage'], ) || agent.kind !== 'claude' || !isControllerTerminalAgentSessionId(agent.sessionId) || !['running', 'stopped'].includes(agent.desiredState as string) || !Number.isSafeInteger(agent.consecutiveFailures) || (agent.consecutiveFailures as number) < 0 || (agent.consecutiveFailures as number) > 1024 || (agent.launchMode !== undefined && !['resume', 'new'].includes(agent.launchMode as string)) || (agent.lastFailure !== undefined && ![ 'binary_missing', 'cwd_rebound', 'session_in_use', 'liveness_unverified', 'spawn_failed', ].includes(agent.lastFailure as string)) || (agent.lastFailureMessage !== undefined && (!isNonEmptyString(agent.lastFailureMessage) || Buffer.byteLength(agent.lastFailureMessage, 'utf8') > 1024)) || (agent.lastFailureMessage !== undefined && agent.lastFailure === undefined) ) { throw new Error('Invalid controller terminal agent metadata.'); } } } else if (valueArg.terminal !== undefined) { throw new Error('Browser resources cannot retain terminal metadata.'); } const hasRetiringAt = valueArg.retiringAt !== undefined; const hasRetiredAt = valueArg.retiredAt !== undefined; if ( (hasRetiringAt && !isValidDate(valueArg.retiringAt)) || (hasRetiredAt && !isValidDate(valueArg.retiredAt)) || (valueArg.lifecycle === 'active' && (hasRetiringAt || hasRetiredAt)) || (valueArg.lifecycle === 'retiring' && (!hasRetiringAt || hasRetiredAt)) || (valueArg.lifecycle === 'retired' && (!hasRetiringAt || !hasRetiredAt)) ) { throw new Error('Invalid controller resource lifecycle.'); } } @plugins.smartdata.managed({ collectionName: 'opencode_controller_resources' }) @plugins.smartdata.exactPersistence({ assertDocument: assertControllerResourceDocument, }) export class ControllerResourceModel extends plugins.smartdata.SmartDataDbDoc { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public controllerId!: string; @plugins.smartdata.svDb() public projectId!: string; @plugins.smartdata.svDb() public kind!: IControllerResourceDocument['kind']; @plugins.smartdata.svDb() public title: string = ''; @plugins.smartdata.svDb() public attachmentAuthorityId!: string; @plugins.smartdata.svDb() public attachmentRevision!: number; @plugins.smartdata.svDb() public attachments!: IControllerResourceDocument['attachments']; @plugins.smartdata.svDb() public pendingAttachment?: IControllerResourceDocument['pendingAttachment']; @plugins.smartdata.svDb() public lifecycle!: IControllerResourceDocument['lifecycle']; @plugins.smartdata.svDb() public terminal?: IControllerResourceDocument['terminal']; @plugins.smartdata.svDb() public createdAt!: Date; @plugins.smartdata.svDb() public updatedAt!: Date; @plugins.smartdata.svDb() public retiringAt?: Date; @plugins.smartdata.index({ expireAfterSeconds: 90 * 24 * 60 * 60 }) public retiredAt?: Date; @plugins.smartdata.svDb() public updateId!: string; } const maxSessionStateExchanges = 8; const maxSessionStateExchangeBytes = 256 * 1024; export const controllerSessionStateId = ( controllerIdArg: string, projectIdArg: string, sessionIdArg: IControllerRuntimeId, ): string => `sessionstate:v1:${plugins.crypto .createHash('sha256') .update(JSON.stringify([ controllerIdArg, projectIdArg, sessionIdArg.harnessId, sessionIdArg.nativeId, ])) .digest('base64url')}`; export function assertControllerSessionStateDocument( valueArg: unknown, ): asserts valueArg is IControllerSessionStateDocument { if (!isPlainObject(valueArg)) throw new Error('Invalid controller session state document.'); if ( !hasExactKeys( valueArg, [ 'id', 'controllerId', 'projectId', 'sessionId', 'scratchpad', 'intelligenceExchanges', 'updateId', ], ['modelChoice', 'providerConnectionId', 'flexProjectManagement', 'deletedAt'], ) || !isControllerId(valueArg.controllerId) || !isNonEmptyString(valueArg.projectId) || !groupIdentifierPattern.test(valueArg.projectId) || !isRuntimeId(valueArg.sessionId, ['opencode', 'flex', 'codex']) || (valueArg.modelChoice !== undefined && ( !isModelChoice(valueArg.modelChoice) || valueArg.modelChoice.harnessId !== valueArg.sessionId.harnessId )) || (valueArg.providerConnectionId !== undefined && ( valueArg.sessionId.harnessId !== 'flex' || valueArg.modelChoice?.harnessId !== 'flex' || !isNonEmptyString(valueArg.providerConnectionId) || Buffer.byteLength(valueArg.providerConnectionId, 'utf8') > 512 )) || valueArg.id !== controllerSessionStateId( valueArg.controllerId, valueArg.projectId, valueArg.sessionId, ) || !isBase64Url(valueArg.updateId, 32) || (Object.hasOwn(valueArg, 'deletedAt') && !isValidDate(valueArg.deletedAt)) || !isPlainObject(valueArg.scratchpad) || !hasExactKeys( valueArg.scratchpad, ['text', 'revision'], ['updatedAt', 'updatedBy'], ) || typeof valueArg.scratchpad.text !== 'string' || valueArg.scratchpad.text.length > 32_768 || Buffer.byteLength(valueArg.scratchpad.text, 'utf8') > 128 * 1024 || !Number.isSafeInteger(valueArg.scratchpad.revision) || (valueArg.scratchpad.revision as number) < 0 ) { throw new Error('Invalid controller session state document.'); } const hasUpdatedAt = Object.hasOwn(valueArg.scratchpad, 'updatedAt'); const hasUpdatedBy = Object.hasOwn(valueArg.scratchpad, 'updatedBy'); if ( hasUpdatedAt !== hasUpdatedBy || (hasUpdatedAt && !isValidDate(valueArg.scratchpad.updatedAt)) || ( hasUpdatedBy && valueArg.scratchpad.updatedBy !== 'user' && valueArg.scratchpad.updatedBy !== 'intelligence' && valueArg.scratchpad.updatedBy !== 'agent' && valueArg.scratchpad.updatedBy !== 'application' ) || ((valueArg.scratchpad.revision as number) === 0 && hasUpdatedAt) || ((valueArg.scratchpad.revision as number) > 0 && !hasUpdatedAt) || !Array.isArray(valueArg.intelligenceExchanges) || valueArg.intelligenceExchanges.length > maxSessionStateExchanges || Buffer.byteLength(JSON.stringify(valueArg.intelligenceExchanges), 'utf8') > maxSessionStateExchangeBytes ) { throw new Error('Invalid controller session state document.'); } const exchangeIds = new Set(); for (const exchange of valueArg.intelligenceExchanges) { if ( !isPlainObject(exchange) || !hasExactKeys( exchange, ['id', 'question', 'status', 'createdAt'], [ 'answer', 'error', 'model', 'scratchpadConflict', 'scratchpadRevisionAtAdmission', 'temporarySessionId', 'completedAt', ], ) || !isBase64Url(exchange.id, 16) || exchangeIds.has(exchange.id) || !isNonEmptyString(exchange.question) || exchange.question.length > 8_192 || Buffer.byteLength(exchange.question, 'utf8') > 32 * 1024 || !['running', 'completed', 'error'].includes(exchange.status as string) || !isValidDate(exchange.createdAt) || (Object.hasOwn(exchange, 'completedAt') && !isValidDate(exchange.completedAt)) || (Object.hasOwn(exchange, 'answer') && (typeof exchange.answer !== 'string' || exchange.answer.length > 32_768 || Buffer.byteLength(exchange.answer, 'utf8') > 128 * 1024)) || (Object.hasOwn(exchange, 'error') && (typeof exchange.error !== 'string' || exchange.error.length < 1 || Buffer.byteLength(exchange.error, 'utf8') > 16 * 1024)) || (Object.hasOwn(exchange, 'model') && (!isNonEmptyString(exchange.model) || Buffer.byteLength(exchange.model, 'utf8') > 512)) || (Object.hasOwn(exchange, 'scratchpadConflict') && exchange.scratchpadConflict !== true) || (Object.hasOwn(exchange, 'scratchpadRevisionAtAdmission') && ( !Number.isSafeInteger(exchange.scratchpadRevisionAtAdmission) || (exchange.scratchpadRevisionAtAdmission as number) < 0 )) || (Object.hasOwn(exchange, 'temporarySessionId') && ( !isNonEmptyString(exchange.temporarySessionId) || Buffer.byteLength(exchange.temporarySessionId, 'utf8') > 512 )) ) { throw new Error('Invalid controller session intelligence exchange.'); } exchangeIds.add(exchange.id); if ( (exchange.status === 'running' && ( Object.hasOwn(exchange, 'answer') || Object.hasOwn(exchange, 'error') || Object.hasOwn(exchange, 'completedAt') || Object.hasOwn(exchange, 'scratchpadConflict') || !Number.isSafeInteger(exchange.scratchpadRevisionAtAdmission) )) || (exchange.status === 'completed' && ( typeof exchange.answer !== 'string' || !isValidDate(exchange.completedAt) || Object.hasOwn(exchange, 'error') || Object.hasOwn(exchange, 'scratchpadRevisionAtAdmission') )) || (exchange.status === 'error' && ( !isNonEmptyString(exchange.error) || !isValidDate(exchange.completedAt) || Object.hasOwn(exchange, 'answer') || Object.hasOwn(exchange, 'scratchpadConflict') || Object.hasOwn(exchange, 'scratchpadRevisionAtAdmission') )) ) { throw new Error('Invalid controller session intelligence exchange state.'); } } if (valueArg.flexProjectManagement !== undefined) { const projectManagement = valueArg.flexProjectManagement; plugins.flexharness.assertFlexProjectManagementRecord(projectManagement); const isTombstone = 'deletedAt' in projectManagement; const deletedAt = valueArg.deletedAt as Date | undefined; if ( valueArg.sessionId.harnessId !== 'flex' || isTombstone !== (deletedAt !== undefined) || ( !isTombstone && projectManagement.scratchpad !== valueArg.scratchpad.text ) || ( isTombstone && projectManagement.deletedAt !== deletedAt?.toISOString() ) ) { throw new Error('Invalid controller Flex project-management state.'); } } if ( valueArg.deletedAt !== undefined && ( valueArg.scratchpad.text !== '' || valueArg.intelligenceExchanges.length !== 0 || valueArg.modelChoice !== undefined || valueArg.providerConnectionId !== undefined ) ) { throw new Error('Deleted controller session state must not retain content.'); } } @plugins.smartdata.compoundIndex({ name: 'controller_session_state_scope', key: { controllerId: 1, projectId: 1, 'sessionId.harnessId': 1, id: 1, }, }) @plugins.smartdata.managed({ collectionName: 'opencode_controller_session_states' }) @plugins.smartdata.exactPersistence({ assertDocument: assertControllerSessionStateDocument, }) export class ControllerSessionStateModel extends plugins.smartdata.SmartDataDbDoc { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public controllerId!: string; @plugins.smartdata.svDb() public projectId!: string; @plugins.smartdata.svDb() public sessionId!: IControllerRuntimeId; @plugins.smartdata.svDb() public scratchpad!: IControllerSessionStateDocument['scratchpad']; @plugins.smartdata.svDb() public intelligenceExchanges!: IControllerSessionStateDocument['intelligenceExchanges']; @plugins.smartdata.svDb() public flexProjectManagement?: IControllerSessionStateDocument['flexProjectManagement']; @plugins.smartdata.svDb() public modelChoice?: TControllerModelChoice; @plugins.smartdata.svDb() public providerConnectionId?: string; @plugins.smartdata.svDb() public updateId!: string; @plugins.smartdata.index({ expireAfterSeconds: 30 * 24 * 60 * 60 }) public deletedAt?: Date; } export const assertControllerTempPasswordDocument = ( valueArg: unknown, ): asserts valueArg is IControllerTempPasswordDocument => { if (!isPlainObject(valueArg)) throw new Error('Invalid controller temp password document.'); if ( !hasExactKeys(valueArg, ['id', 'controllerId', 'secretHash', 'createdAt', 'expiresAt']) || !isNonEmptyString(valueArg.id) || valueArg.id.length > 512 || !isNonEmptyString(valueArg.controllerId) || !/^controller:[1-9][0-9]{0,4}$/.test(valueArg.controllerId) || !isBase64Url(valueArg.secretHash, 32) || !isValidDate(valueArg.createdAt) || !isValidDate(valueArg.expiresAt) || valueArg.expiresAt.getTime() <= valueArg.createdAt.getTime() ) { throw new Error('Invalid controller temp password document.'); } }; @plugins.smartdata.managed({ collectionName: 'opencode_controller_temp_passwords' }) @plugins.smartdata.exactPersistence({ assertDocument: assertControllerTempPasswordDocument, }) export class ControllerTempPasswordModel extends plugins.smartdata.SmartDataDbDoc< ControllerTempPasswordModel, IControllerTempPasswordDocument > { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public controllerId!: string; @plugins.smartdata.svDb() public secretHash!: string; @plugins.smartdata.svDb() public createdAt!: Date; @plugins.smartdata.index({ expireAfterSeconds: 0 }) public expiresAt!: Date; } @plugins.smartdata.managed({ collectionName: 'opencode_controller_audit' }) @plugins.smartdata.exactPersistence({ assertDocument: assertControllerAuditEventDocument, }) export class ControllerAuditEventModel extends plugins.smartdata.SmartDataDbDoc { declare static exact: plugins.smartdata.TExact; @plugins.smartdata.unI() public id!: string; @plugins.smartdata.svDb() public controllerId!: string; @plugins.smartdata.index({ expireAfterSeconds: auditRetentionSeconds }) public timestamp!: Date; @plugins.smartdata.svDb() public type!: IControllerAuditEvent['type']; @plugins.smartdata.svDb() public outcome!: IControllerAuditEvent['outcome']; @plugins.smartdata.svDb() public operationId?: string; @plugins.smartdata.svDb() public peerId?: string; @plugins.smartdata.svDb() public credentialId?: string; @plugins.smartdata.svDb() public sessionId?: TControllerAuditSessionId; @plugins.smartdata.svDb() public requestId?: IControllerRuntimeId; }