@theaiinc/yggdrasil 0.2.1 → 0.3.0
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.
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/dist/src/index.d.ts +6 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/orchestration-controller.d.ts +13 -1
- package/dist/src/orchestration-controller.d.ts.map +1 -1
- package/dist/src/orchestration-controller.js +419 -50
- package/dist/src/orchestration-controller.js.map +1 -1
- package/dist/src/services/realm-lifecycle.d.ts +49 -0
- package/dist/src/services/realm-lifecycle.d.ts.map +1 -0
- package/dist/src/services/realm-lifecycle.js +154 -0
- package/dist/src/services/realm-lifecycle.js.map +1 -0
- package/dist/src/services/realm-provisioner.d.ts +45 -0
- package/dist/src/services/realm-provisioner.d.ts.map +1 -0
- package/dist/src/services/realm-provisioner.js +102 -0
- package/dist/src/services/realm-provisioner.js.map +1 -0
- package/dist/src/services/realm-registry.d.ts +83 -0
- package/dist/src/services/realm-registry.d.ts.map +1 -0
- package/dist/src/services/realm-registry.js +136 -0
- package/dist/src/services/realm-registry.js.map +1 -0
- package/dist/src/services/realm-scheduler.d.ts +47 -0
- package/dist/src/services/realm-scheduler.d.ts.map +1 -0
- package/dist/src/services/realm-scheduler.js +112 -0
- package/dist/src/services/realm-scheduler.js.map +1 -0
- package/dist/src/types/index.d.ts +270 -1
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +6 -1
- package/dist/src/types/index.js.map +1 -1
- package/package.json +25 -5
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RealmLifecycleService — handles Realm registration, heartbeat, and stale detection.
|
|
3
|
+
*
|
|
4
|
+
* Yggdrasil manages the lifecycle of Realm instances. Ratatoskr is the transport
|
|
5
|
+
* that relays messages from Realm to Yggdrasil — it never stores or decides on realm state.
|
|
6
|
+
*
|
|
7
|
+
* Lifecycle:
|
|
8
|
+
* creating ──→ running ──→ unhealthy ──→ destroyed
|
|
9
|
+
* ↑ │
|
|
10
|
+
* └──── recover ┘
|
|
11
|
+
*
|
|
12
|
+
* Stale detection: if a realm has not heartbeated within the stale TTL,
|
|
13
|
+
* Yggdrasil marks it unhealthy automatically.
|
|
14
|
+
*/
|
|
15
|
+
import type { Realm, RealmRegistration, RealmHeartbeat, RealmDeregistration, RealmTemplateType } from '../types/index.js';
|
|
16
|
+
import { RealmRegistry } from './realm-registry.js';
|
|
17
|
+
export declare class RealmLifecycleService {
|
|
18
|
+
private readonly registry;
|
|
19
|
+
private readonly staleTtlMs;
|
|
20
|
+
private staleTimer;
|
|
21
|
+
private registered;
|
|
22
|
+
constructor(registry: RealmRegistry, staleTtlMs?: number);
|
|
23
|
+
/**
|
|
24
|
+
* Register a realm that has just come online.
|
|
25
|
+
* Transitions the realm from 'creating' → 'running' and sets its endpoints.
|
|
26
|
+
* If the realm is not yet in the registry (spawned by an external process),
|
|
27
|
+
* it is added directly.
|
|
28
|
+
*/
|
|
29
|
+
registerRealm(registration: RealmRegistration, templateType: RealmTemplateType): Realm;
|
|
30
|
+
/**
|
|
31
|
+
* Process a realm heartbeat.
|
|
32
|
+
* Updates lastHeartbeat timestamp and recovers from unhealthy state.
|
|
33
|
+
*/
|
|
34
|
+
heartbeatRealm(heartbeat: RealmHeartbeat): Realm | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Process a realm deregistration (intentional shutdown).
|
|
37
|
+
*/
|
|
38
|
+
deregisterRealm(deregistration: RealmDeregistration): void;
|
|
39
|
+
/**
|
|
40
|
+
* Start stale detection timer.
|
|
41
|
+
* Checks all running realms periodically and marks stale ones as unhealthy.
|
|
42
|
+
*/
|
|
43
|
+
startStaleDetection(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Stop stale detection timer.
|
|
46
|
+
*/
|
|
47
|
+
stopStaleDetection(): void;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=realm-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realm-lifecycle.d.ts","sourceRoot":"","sources":["../../../src/services/realm-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC1H,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOpD,qBAAa,qBAAqB;IAK9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAL7B,OAAO,CAAC,UAAU,CAA6C;IAC/D,OAAO,CAAC,UAAU,CAAkB;gBAGjB,QAAQ,EAAE,aAAa,EACvB,UAAU,GAAE,MAA6B;IAG5D;;;;;OAKG;IACH,aAAa,CAAC,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,iBAAiB,GAAG,KAAK;IA+CtF;;;OAGG;IACH,cAAc,CAAC,SAAS,EAAE,cAAc,GAAG,KAAK,GAAG,SAAS;IAmB5D;;OAEG;IACH,eAAe,CAAC,cAAc,EAAE,mBAAmB,GAAG,IAAI;IAiB1D;;;OAGG;IACH,mBAAmB,IAAI,IAAI;IA8B3B;;OAEG;IACH,kBAAkB,IAAI,IAAI;CAO3B"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RealmLifecycleService — handles Realm registration, heartbeat, and stale detection.
|
|
3
|
+
*
|
|
4
|
+
* Yggdrasil manages the lifecycle of Realm instances. Ratatoskr is the transport
|
|
5
|
+
* that relays messages from Realm to Yggdrasil — it never stores or decides on realm state.
|
|
6
|
+
*
|
|
7
|
+
* Lifecycle:
|
|
8
|
+
* creating ──→ running ──→ unhealthy ──→ destroyed
|
|
9
|
+
* ↑ │
|
|
10
|
+
* └──── recover ┘
|
|
11
|
+
*
|
|
12
|
+
* Stale detection: if a realm has not heartbeated within the stale TTL,
|
|
13
|
+
* Yggdrasil marks it unhealthy automatically.
|
|
14
|
+
*/
|
|
15
|
+
import { getLogger } from './logger.js';
|
|
16
|
+
const DEFAULT_STALE_TTL_MS = 60_000; // 60s without heartbeat → unhealthy
|
|
17
|
+
const STALE_CHECK_INTERVAL_MS = 10_000; // check every 10s
|
|
18
|
+
const logger = getLogger();
|
|
19
|
+
export class RealmLifecycleService {
|
|
20
|
+
registry;
|
|
21
|
+
staleTtlMs;
|
|
22
|
+
staleTimer;
|
|
23
|
+
registered = false;
|
|
24
|
+
constructor(registry, staleTtlMs = DEFAULT_STALE_TTL_MS) {
|
|
25
|
+
this.registry = registry;
|
|
26
|
+
this.staleTtlMs = staleTtlMs;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Register a realm that has just come online.
|
|
30
|
+
* Transitions the realm from 'creating' → 'running' and sets its endpoints.
|
|
31
|
+
* If the realm is not yet in the registry (spawned by an external process),
|
|
32
|
+
* it is added directly.
|
|
33
|
+
*/
|
|
34
|
+
registerRealm(registration, templateType) {
|
|
35
|
+
const now = new Date().toISOString();
|
|
36
|
+
const existing = this.registry.getRealm(registration.realmId);
|
|
37
|
+
if (existing) {
|
|
38
|
+
// Realm was pre-registered by RealmProvisioner — update it
|
|
39
|
+
existing.state = 'running';
|
|
40
|
+
existing.endpoints = registration.endpoints;
|
|
41
|
+
existing.lastHeartbeat = now;
|
|
42
|
+
existing.updatedAt = now;
|
|
43
|
+
logger.info('Realm registered (updated existing)', {
|
|
44
|
+
realmId: registration.realmId,
|
|
45
|
+
runnerId: registration.runnerId,
|
|
46
|
+
template: templateType,
|
|
47
|
+
});
|
|
48
|
+
return existing;
|
|
49
|
+
}
|
|
50
|
+
// Realm was spawned externally (e.g. manually) — add to registry
|
|
51
|
+
const realm = {
|
|
52
|
+
id: registration.realmId,
|
|
53
|
+
templateId: templateType,
|
|
54
|
+
runnerId: registration.runnerId,
|
|
55
|
+
state: 'running',
|
|
56
|
+
endpoints: { ...registration.endpoints },
|
|
57
|
+
createdAt: now,
|
|
58
|
+
updatedAt: now,
|
|
59
|
+
lastHeartbeat: now,
|
|
60
|
+
};
|
|
61
|
+
// Create a minimal template from registration data
|
|
62
|
+
const template = {
|
|
63
|
+
id: `${registration.realmId}-template`,
|
|
64
|
+
type: templateType,
|
|
65
|
+
capabilities: registration.capabilities,
|
|
66
|
+
};
|
|
67
|
+
this.registry.addRealm(realm, template);
|
|
68
|
+
logger.info('Realm registered (new)', {
|
|
69
|
+
realmId: registration.realmId,
|
|
70
|
+
runnerId: registration.runnerId,
|
|
71
|
+
template: templateType,
|
|
72
|
+
});
|
|
73
|
+
return realm;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Process a realm heartbeat.
|
|
77
|
+
* Updates lastHeartbeat timestamp and recovers from unhealthy state.
|
|
78
|
+
*/
|
|
79
|
+
heartbeatRealm(heartbeat) {
|
|
80
|
+
const realm = this.registry.getRealm(heartbeat.realmId);
|
|
81
|
+
if (!realm) {
|
|
82
|
+
logger.warn('Heartbeat for unknown realm', { realmId: heartbeat.realmId });
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
realm.lastHeartbeat = new Date().toISOString();
|
|
86
|
+
realm.updatedAt = realm.lastHeartbeat;
|
|
87
|
+
// Recover from unhealthy state
|
|
88
|
+
if (realm.state === 'unhealthy' && heartbeat.healthy) {
|
|
89
|
+
realm.state = 'running';
|
|
90
|
+
logger.info('Realm recovered from unhealthy', { realmId: heartbeat.realmId });
|
|
91
|
+
}
|
|
92
|
+
return realm;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Process a realm deregistration (intentional shutdown).
|
|
96
|
+
*/
|
|
97
|
+
deregisterRealm(deregistration) {
|
|
98
|
+
const realm = this.registry.getRealm(deregistration.realmId);
|
|
99
|
+
if (!realm) {
|
|
100
|
+
logger.warn('Deregistration for unknown realm', { realmId: deregistration.realmId });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
realm.state = 'destroyed';
|
|
104
|
+
realm.updatedAt = new Date().toISOString();
|
|
105
|
+
this.registry.removeRealm(deregistration.realmId);
|
|
106
|
+
logger.info('Realm deregistered', {
|
|
107
|
+
realmId: deregistration.realmId,
|
|
108
|
+
reason: deregistration.reason,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Start stale detection timer.
|
|
113
|
+
* Checks all running realms periodically and marks stale ones as unhealthy.
|
|
114
|
+
*/
|
|
115
|
+
startStaleDetection() {
|
|
116
|
+
if (this.staleTimer)
|
|
117
|
+
return;
|
|
118
|
+
this.registered = true;
|
|
119
|
+
this.staleTimer = setInterval(() => {
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
const stale = [];
|
|
122
|
+
for (const realm of this.registry.listRealms()) {
|
|
123
|
+
if (realm.state !== 'running')
|
|
124
|
+
continue;
|
|
125
|
+
if (!realm.lastHeartbeat) {
|
|
126
|
+
// No heartbeat yet — give it time
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const elapsed = now - new Date(realm.lastHeartbeat).getTime();
|
|
130
|
+
if (elapsed > this.staleTtlMs) {
|
|
131
|
+
stale.push(realm.id);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const realmId of stale) {
|
|
135
|
+
this.registry.updateRealmState(realmId, 'unhealthy');
|
|
136
|
+
logger.warn('Realm marked unhealthy due to heartbeat timeout', {
|
|
137
|
+
realmId,
|
|
138
|
+
missedBy: `${Math.round((now - new Date(this.registry.getRealm(realmId)?.lastHeartbeat ?? now).getTime()) / 1000)}s`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}, STALE_CHECK_INTERVAL_MS);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Stop stale detection timer.
|
|
145
|
+
*/
|
|
146
|
+
stopStaleDetection() {
|
|
147
|
+
if (this.staleTimer) {
|
|
148
|
+
clearInterval(this.staleTimer);
|
|
149
|
+
this.staleTimer = undefined;
|
|
150
|
+
}
|
|
151
|
+
this.registered = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=realm-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realm-lifecycle.js","sourceRoot":"","sources":["../../../src/services/realm-lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RealmProvisioner — ensures a realm exists for a given allocation decision.
|
|
3
|
+
*
|
|
4
|
+
* Yggdrasil decides. RealmProvisioner executes.
|
|
5
|
+
*
|
|
6
|
+
* If the allocation says "spawn", it sends a command to the runner (via
|
|
7
|
+
* Ratatoskr's task mechanism) to create the Realm container/VM and waits
|
|
8
|
+
* for the endpoints to be ready.
|
|
9
|
+
*
|
|
10
|
+
* If the allocation says "attach", it returns the existing realm.
|
|
11
|
+
*
|
|
12
|
+
* Future:
|
|
13
|
+
* - Synchronous spawn via Ratatoskr task endpoint
|
|
14
|
+
* - Pool management (pre-warm idle realms)
|
|
15
|
+
* - Retry with backoff on failed spawns
|
|
16
|
+
* - Veto check: if a runner rejects a spawn, fall through to next candidate
|
|
17
|
+
*/
|
|
18
|
+
import type { Realm, RealmAllocation } from '../types/index.js';
|
|
19
|
+
import { RealmRegistry } from './realm-registry.js';
|
|
20
|
+
export declare class RealmProvisioner {
|
|
21
|
+
private readonly registry;
|
|
22
|
+
constructor(registry: RealmRegistry);
|
|
23
|
+
/**
|
|
24
|
+
* Ensure a realm matching the allocation exists.
|
|
25
|
+
* Returns the realm (either existing or newly created).
|
|
26
|
+
*/
|
|
27
|
+
ensureRealm(allocation: RealmAllocation, ownerId?: string): Promise<Realm>;
|
|
28
|
+
/**
|
|
29
|
+
* Create (or mark as creating) a new realm instance.
|
|
30
|
+
*
|
|
31
|
+
* In v1 this registers the realm in-memory with a 'creating' state.
|
|
32
|
+
* The async spawn happens when the runner acknowledges and reports back
|
|
33
|
+
* with the actual endpoints.
|
|
34
|
+
*/
|
|
35
|
+
private spawnRealm;
|
|
36
|
+
/**
|
|
37
|
+
* Update realm endpoints and state (called when a runner reports back).
|
|
38
|
+
*/
|
|
39
|
+
updateRealmEndpoints(realmId: string, state: Realm['state'], endpoints: Realm['endpoints']): void;
|
|
40
|
+
/**
|
|
41
|
+
* Destroy a realm instance.
|
|
42
|
+
*/
|
|
43
|
+
destroyRealm(realmId: string): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=realm-provisioner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realm-provisioner.d.ts","sourceRoot":"","sources":["../../../src/services/realm-provisioner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,eAAe,EAAiB,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,aAAa;IAEpD;;;OAGG;IACG,WAAW,CAAC,UAAU,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAWhF;;;;;;OAMG;YACW,UAAU;IA4CxB;;OAEG;IACH,oBAAoB,CAClB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,EACrB,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,GAC5B,IAAI;IASP;;OAEG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAKnD"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RealmProvisioner — ensures a realm exists for a given allocation decision.
|
|
3
|
+
*
|
|
4
|
+
* Yggdrasil decides. RealmProvisioner executes.
|
|
5
|
+
*
|
|
6
|
+
* If the allocation says "spawn", it sends a command to the runner (via
|
|
7
|
+
* Ratatoskr's task mechanism) to create the Realm container/VM and waits
|
|
8
|
+
* for the endpoints to be ready.
|
|
9
|
+
*
|
|
10
|
+
* If the allocation says "attach", it returns the existing realm.
|
|
11
|
+
*
|
|
12
|
+
* Future:
|
|
13
|
+
* - Synchronous spawn via Ratatoskr task endpoint
|
|
14
|
+
* - Pool management (pre-warm idle realms)
|
|
15
|
+
* - Retry with backoff on failed spawns
|
|
16
|
+
* - Veto check: if a runner rejects a spawn, fall through to next candidate
|
|
17
|
+
*/
|
|
18
|
+
import { nanoid } from 'nanoid';
|
|
19
|
+
export class RealmProvisioner {
|
|
20
|
+
registry;
|
|
21
|
+
constructor(registry) {
|
|
22
|
+
this.registry = registry;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ensure a realm matching the allocation exists.
|
|
26
|
+
* Returns the realm (either existing or newly created).
|
|
27
|
+
*/
|
|
28
|
+
async ensureRealm(allocation, ownerId) {
|
|
29
|
+
if (allocation.action === 'attach' && allocation.realmId) {
|
|
30
|
+
const existing = this.registry.getRealm(allocation.realmId);
|
|
31
|
+
if (existing) {
|
|
32
|
+
return existing;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return this.spawnRealm(allocation, ownerId);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create (or mark as creating) a new realm instance.
|
|
39
|
+
*
|
|
40
|
+
* In v1 this registers the realm in-memory with a 'creating' state.
|
|
41
|
+
* The async spawn happens when the runner acknowledges and reports back
|
|
42
|
+
* with the actual endpoints.
|
|
43
|
+
*/
|
|
44
|
+
async spawnRealm(allocation, ownerId) {
|
|
45
|
+
const now = new Date().toISOString();
|
|
46
|
+
const realmId = `realm-${nanoid(12)}`;
|
|
47
|
+
const realm = {
|
|
48
|
+
id: realmId,
|
|
49
|
+
templateId: allocation.template.id,
|
|
50
|
+
runnerId: allocation.runnerId,
|
|
51
|
+
...(ownerId !== undefined ? { ownerId } : {}),
|
|
52
|
+
state: 'creating',
|
|
53
|
+
endpoints: {
|
|
54
|
+
observation: '',
|
|
55
|
+
input: '',
|
|
56
|
+
},
|
|
57
|
+
createdAt: now,
|
|
58
|
+
updatedAt: now,
|
|
59
|
+
};
|
|
60
|
+
this.registry.addRealm(realm, allocation.template);
|
|
61
|
+
// In a full implementation, this would send a command to the runner:
|
|
62
|
+
// POST /runners/:runnerId/tasks
|
|
63
|
+
// { type: "spawn_realm", template: template.type, realmId }
|
|
64
|
+
//
|
|
65
|
+
// The runner would Docker exec the realm container, wait for it,
|
|
66
|
+
// then POST back to Yggdrasil:
|
|
67
|
+
// PATCH /api/v1/realms/:realmId
|
|
68
|
+
// { state: "running", endpoints: { observation, input } }
|
|
69
|
+
//
|
|
70
|
+
// The runner may also veto by returning a failed task.
|
|
71
|
+
//
|
|
72
|
+
// For now, simulate a short spawn delay and set placeholder endpoints.
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
74
|
+
realm.state = 'running';
|
|
75
|
+
realm.endpoints = {
|
|
76
|
+
observation: `http://realm-placeholder:8542/api/v1/realms/${realmId}/capture`,
|
|
77
|
+
input: `http://realm-placeholder:8542/api/v1/realms/${realmId}`,
|
|
78
|
+
};
|
|
79
|
+
realm.updatedAt = new Date().toISOString();
|
|
80
|
+
return realm;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Update realm endpoints and state (called when a runner reports back).
|
|
84
|
+
*/
|
|
85
|
+
updateRealmEndpoints(realmId, state, endpoints) {
|
|
86
|
+
this.registry.updateRealmState(realmId, state);
|
|
87
|
+
const entry = this.registry.getEntry(realmId);
|
|
88
|
+
if (entry) {
|
|
89
|
+
entry.realm.endpoints = endpoints;
|
|
90
|
+
entry.realm.updatedAt = new Date().toISOString();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Destroy a realm instance.
|
|
95
|
+
*/
|
|
96
|
+
async destroyRealm(realmId) {
|
|
97
|
+
this.registry.updateRealmState(realmId, 'destroyed');
|
|
98
|
+
// In a full implementation, would send a destroy command to the runner.
|
|
99
|
+
this.registry.removeRealm(realmId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=realm-provisioner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realm-provisioner.js","sourceRoot":"","sources":["../../../src/services/realm-provisioner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RealmRegistry — stores Realm templates advertised by runners and Realm instances.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Tracks RealmTemplate[] per runner (what CAN be spawned)
|
|
6
|
+
* - Tracks Realm[] (what IS running)
|
|
7
|
+
* - Provides lookup by runner, template type, owner, and pool tag
|
|
8
|
+
*
|
|
9
|
+
* Yggdrasil is stateless-in-memory in v1. A future version may add
|
|
10
|
+
* FileSessionStore or database-backed persistence.
|
|
11
|
+
*/
|
|
12
|
+
import type { Realm, RealmTemplate } from '../types/index.js';
|
|
13
|
+
export interface RealmRegistryEntry {
|
|
14
|
+
realm: Realm;
|
|
15
|
+
template: RealmTemplate;
|
|
16
|
+
}
|
|
17
|
+
export declare class RealmRegistry {
|
|
18
|
+
/** Runner ID → realm templates they advertise. */
|
|
19
|
+
private readonly templatesByRunner;
|
|
20
|
+
/** Realm ID → realm instance + its template. */
|
|
21
|
+
private readonly realms;
|
|
22
|
+
/**
|
|
23
|
+
* Register or update realm templates for a runner.
|
|
24
|
+
*/
|
|
25
|
+
setTemplates(runnerId: string, templates: RealmTemplate[]): void;
|
|
26
|
+
/**
|
|
27
|
+
* Get templates for a specific runner.
|
|
28
|
+
*/
|
|
29
|
+
getTemplates(runnerId: string): RealmTemplate[];
|
|
30
|
+
/**
|
|
31
|
+
* Get all templates across all runners that match a given template type.
|
|
32
|
+
*/
|
|
33
|
+
getTemplatesByType(type: string): Array<{
|
|
34
|
+
runnerId: string;
|
|
35
|
+
template: RealmTemplate;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Remove templates for a runner (e.g. on deregistration).
|
|
39
|
+
*/
|
|
40
|
+
removeTemplates(runnerId: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Register a new realm instance.
|
|
43
|
+
*/
|
|
44
|
+
addRealm(realm: Realm, template: RealmTemplate): void;
|
|
45
|
+
/**
|
|
46
|
+
* Get a realm instance by ID.
|
|
47
|
+
*/
|
|
48
|
+
getRealm(realmId: string): Realm | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Get realm + template by ID.
|
|
51
|
+
*/
|
|
52
|
+
getEntry(realmId: string): RealmRegistryEntry | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Find a running realm by owner ID and template type (persistent realm affinity).
|
|
55
|
+
* This is how ownerId becomes a scheduling primitive.
|
|
56
|
+
*/
|
|
57
|
+
findRealmByOwner(ownerId: string, templateType: string): Realm | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Find an idle realm in a pool (by template type, no owner).
|
|
60
|
+
*/
|
|
61
|
+
findPooledRealm(templateType: string, poolTag?: string): Realm | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* List all realm instances.
|
|
64
|
+
*/
|
|
65
|
+
listRealms(): Realm[];
|
|
66
|
+
/**
|
|
67
|
+
* List realms for a specific runner.
|
|
68
|
+
*/
|
|
69
|
+
listRealmsByRunner(runnerId: string): Realm[];
|
|
70
|
+
/**
|
|
71
|
+
* Update realm state.
|
|
72
|
+
*/
|
|
73
|
+
updateRealmState(realmId: string, state: Realm['state']): void;
|
|
74
|
+
/**
|
|
75
|
+
* Remove a realm.
|
|
76
|
+
*/
|
|
77
|
+
removeRealm(realmId: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* Clear all state (useful for testing).
|
|
80
|
+
*/
|
|
81
|
+
clear(): void;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=realm-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realm-registry.d.ts","sourceRoot":"","sources":["../../../src/services/realm-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAmB,MAAM,mBAAmB,CAAC;AAE/E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,qBAAa,aAAa;IACxB,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsC;IAExE,gDAAgD;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAIhE;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,IAAI;IAIhE;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE;IAI/C;;OAEG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,aAAa,CAAA;KAAE,CAAC;IAYtF;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMvC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAIrD;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAI5C;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIzD;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAa1E;;OAEG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAe1E;;OAEG;IACH,UAAU,IAAI,KAAK,EAAE;IAIrB;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,EAAE;IAM7C;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAQ9D;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIlC;;OAEG;IACH,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RealmRegistry — stores Realm templates advertised by runners and Realm instances.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Tracks RealmTemplate[] per runner (what CAN be spawned)
|
|
6
|
+
* - Tracks Realm[] (what IS running)
|
|
7
|
+
* - Provides lookup by runner, template type, owner, and pool tag
|
|
8
|
+
*
|
|
9
|
+
* Yggdrasil is stateless-in-memory in v1. A future version may add
|
|
10
|
+
* FileSessionStore or database-backed persistence.
|
|
11
|
+
*/
|
|
12
|
+
export class RealmRegistry {
|
|
13
|
+
/** Runner ID → realm templates they advertise. */
|
|
14
|
+
templatesByRunner = new Map();
|
|
15
|
+
/** Realm ID → realm instance + its template. */
|
|
16
|
+
realms = new Map();
|
|
17
|
+
// ── Template management ──────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Register or update realm templates for a runner.
|
|
20
|
+
*/
|
|
21
|
+
setTemplates(runnerId, templates) {
|
|
22
|
+
this.templatesByRunner.set(runnerId, templates);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get templates for a specific runner.
|
|
26
|
+
*/
|
|
27
|
+
getTemplates(runnerId) {
|
|
28
|
+
return this.templatesByRunner.get(runnerId) ?? [];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get all templates across all runners that match a given template type.
|
|
32
|
+
*/
|
|
33
|
+
getTemplatesByType(type) {
|
|
34
|
+
const results = [];
|
|
35
|
+
for (const [runnerId, templates] of this.templatesByRunner.entries()) {
|
|
36
|
+
for (const template of templates) {
|
|
37
|
+
if (template.type === type) {
|
|
38
|
+
results.push({ runnerId, template });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Remove templates for a runner (e.g. on deregistration).
|
|
46
|
+
*/
|
|
47
|
+
removeTemplates(runnerId) {
|
|
48
|
+
this.templatesByRunner.delete(runnerId);
|
|
49
|
+
}
|
|
50
|
+
// ── Realm instance management ────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Register a new realm instance.
|
|
53
|
+
*/
|
|
54
|
+
addRealm(realm, template) {
|
|
55
|
+
this.realms.set(realm.id, { realm, template });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get a realm instance by ID.
|
|
59
|
+
*/
|
|
60
|
+
getRealm(realmId) {
|
|
61
|
+
return this.realms.get(realmId)?.realm;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get realm + template by ID.
|
|
65
|
+
*/
|
|
66
|
+
getEntry(realmId) {
|
|
67
|
+
return this.realms.get(realmId);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Find a running realm by owner ID and template type (persistent realm affinity).
|
|
71
|
+
* This is how ownerId becomes a scheduling primitive.
|
|
72
|
+
*/
|
|
73
|
+
findRealmByOwner(ownerId, templateType) {
|
|
74
|
+
for (const [, entry] of this.realms.entries()) {
|
|
75
|
+
if (entry.realm.ownerId === ownerId &&
|
|
76
|
+
entry.template.type === templateType &&
|
|
77
|
+
(entry.realm.state === 'running' || entry.realm.state === 'paused')) {
|
|
78
|
+
return entry.realm;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Find an idle realm in a pool (by template type, no owner).
|
|
85
|
+
*/
|
|
86
|
+
findPooledRealm(templateType, poolTag) {
|
|
87
|
+
for (const [, entry] of this.realms.entries()) {
|
|
88
|
+
if (entry.realm.state === 'running' &&
|
|
89
|
+
entry.template.type === templateType &&
|
|
90
|
+
entry.realm.ownerId === undefined) {
|
|
91
|
+
if (poolTag === undefined || entry.realm.poolTag === poolTag) {
|
|
92
|
+
return entry.realm;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* List all realm instances.
|
|
100
|
+
*/
|
|
101
|
+
listRealms() {
|
|
102
|
+
return Array.from(this.realms.values()).map((e) => e.realm);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* List realms for a specific runner.
|
|
106
|
+
*/
|
|
107
|
+
listRealmsByRunner(runnerId) {
|
|
108
|
+
return Array.from(this.realms.values())
|
|
109
|
+
.filter((e) => e.realm.runnerId === runnerId)
|
|
110
|
+
.map((e) => e.realm);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Update realm state.
|
|
114
|
+
*/
|
|
115
|
+
updateRealmState(realmId, state) {
|
|
116
|
+
const entry = this.realms.get(realmId);
|
|
117
|
+
if (entry) {
|
|
118
|
+
entry.realm.state = state;
|
|
119
|
+
entry.realm.updatedAt = new Date().toISOString();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Remove a realm.
|
|
124
|
+
*/
|
|
125
|
+
removeRealm(realmId) {
|
|
126
|
+
this.realms.delete(realmId);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Clear all state (useful for testing).
|
|
130
|
+
*/
|
|
131
|
+
clear() {
|
|
132
|
+
this.templatesByRunner.clear();
|
|
133
|
+
this.realms.clear();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=realm-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realm-registry.js","sourceRoot":"","sources":["../../../src/services/realm-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RealmScheduler — makes allocation decisions for session requests.
|
|
3
|
+
*
|
|
4
|
+
* Yggdrasil owns all scheduling policy:
|
|
5
|
+
* - Which realm type?
|
|
6
|
+
* - Which owner?
|
|
7
|
+
* - Reuse allowed?
|
|
8
|
+
* - Spawn allowed?
|
|
9
|
+
* - Priority?
|
|
10
|
+
*
|
|
11
|
+
* Ratatoskr reports facts (CPU, memory, realms, templates).
|
|
12
|
+
* Yggdrasil decides. Ratatoskr may veto ("I cannot do that") but
|
|
13
|
+
* must never suggest alternatives — that is orchestration.
|
|
14
|
+
*
|
|
15
|
+
* Current implementation:
|
|
16
|
+
* Simple first-fit by template match + online status.
|
|
17
|
+
*
|
|
18
|
+
* Future:
|
|
19
|
+
* Resource-aware, owner-aware, cost-aware, affinity-aware,
|
|
20
|
+
* informed by Ratatoskr's /state endpoint.
|
|
21
|
+
*/
|
|
22
|
+
import type { CreateSessionRequest, RealmAllocation, SessionCapability, RunnerInfo } from '../types/index.js';
|
|
23
|
+
import { RealmRegistry } from './realm-registry.js';
|
|
24
|
+
export declare class RealmScheduler {
|
|
25
|
+
private readonly registry;
|
|
26
|
+
private readonly getRunner;
|
|
27
|
+
constructor(registry: RealmRegistry, getRunner: (runnerId: string) => RunnerInfo | undefined);
|
|
28
|
+
/**
|
|
29
|
+
* Decide how to allocate a realm for a session request.
|
|
30
|
+
*
|
|
31
|
+
* Yggdrasil decides:
|
|
32
|
+
* - Which runner
|
|
33
|
+
* - Which template
|
|
34
|
+
* - Whether to spawn new or attach to existing
|
|
35
|
+
*
|
|
36
|
+
* Priority order:
|
|
37
|
+
* 1. Attach to existing realm owned by this owner (persistent realm)
|
|
38
|
+
* 2. Attach to a pooled idle realm (pre-warmed)
|
|
39
|
+
* 3. Spawn a new realm on a healthy runner
|
|
40
|
+
*/
|
|
41
|
+
schedule(request: CreateSessionRequest): Promise<RealmAllocation>;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve default capabilities for a template type when not explicitly provided.
|
|
44
|
+
*/
|
|
45
|
+
static defaultCapabilitiesFor(type: string): SessionCapability[];
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=realm-scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realm-scheduler.d.ts","sourceRoot":"","sources":["../../../src/services/realm-scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACV,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,UAAU,EACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAoBpD,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS;IAG1E;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAqDvE;;OAEG;IACH,MAAM,CAAC,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE;CAGjE"}
|