@yeaft/webchat-agent 1.0.385 → 1.0.387
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/Dockerfile +26 -0
- package/cli.js +5 -4
- package/connection/index.js +2 -12
- package/container-cli.js +86 -0
- package/container-manager.js +165 -0
- package/index.js +5 -5
- package/local-runtime/server/config.js +8 -37
- package/local-runtime/server/container-agent-service.js +78 -0
- package/local-runtime/server/database.js +0 -1
- package/local-runtime/server/db/connection.js +5 -6
- package/local-runtime/server/db/user-db.js +8 -95
- package/local-runtime/server/index.js +13 -40
- package/local-runtime/server/routes/sandbox-routes.js +28 -95
- package/local-runtime/server/routes/user-routes.js +4 -0
- package/local-runtime/server/ws-agent.js +11 -27
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +96 -103
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +1 -1
- package/package.json +4 -1
- package/local-runtime/server/db/sandbox-db.js +0 -673
- package/local-runtime/server/sandbox-agent-auth.js +0 -66
- package/local-runtime/server/sandbox-attestation-listener.js +0 -128
- package/local-runtime/server/sandbox-config.js +0 -42
- package/local-runtime/server/sandbox-host-attestation.js +0 -175
- package/local-runtime/server/sandbox-reconciler.js +0 -355
- package/managed-sandbox/agent-runtime.js +0 -73
- package/managed-sandbox/controller.js +0 -118
- package/managed-sandbox/helper.js +0 -437
- package/managed-sandbox/identity-store.js +0 -9
- package/managed-sandbox/runtime-executor.js +0 -387
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { sandboxDb } from './database.js';
|
|
2
|
-
import { agents } from './context.js';
|
|
3
|
-
|
|
4
|
-
function readClaims(message) {
|
|
5
|
-
const claims = message?.sandboxClaims;
|
|
6
|
-
if (!claims || typeof claims !== 'object') return null;
|
|
7
|
-
if (typeof claims.sandboxId !== 'string' || typeof claims.instanceId !== 'string'
|
|
8
|
-
|| !Number.isInteger(claims.generation) || typeof claims.imageDigest !== 'string') {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
return claims;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Authenticate a managed Sandbox Agent without accepting the user's general
|
|
16
|
-
* Agent secret. URL identity and credential scope must describe the same
|
|
17
|
-
* immutable Sandbox instance.
|
|
18
|
-
*/
|
|
19
|
-
export function authenticateSandboxAgent(message, pending, store = sandboxDb) {
|
|
20
|
-
if (message?.authKind !== 'sandbox' || typeof message.credentialId !== 'string'
|
|
21
|
-
|| typeof message.secret !== 'string') {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
const claims = readClaims(message);
|
|
25
|
-
if (!claims || pending.instanceId !== claims.instanceId || pending.agentId !== claims.sandboxId) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
try {
|
|
29
|
-
const authenticated = store.authenticateCredential(message.credentialId, message.secret, claims);
|
|
30
|
-
if (authenticated.sandboxId !== claims.sandboxId) return null;
|
|
31
|
-
return {
|
|
32
|
-
...authenticated,
|
|
33
|
-
instanceId: claims.instanceId,
|
|
34
|
-
generation: claims.generation,
|
|
35
|
-
imageDigest: claims.imageDigest,
|
|
36
|
-
sessionKey: message.secret
|
|
37
|
-
};
|
|
38
|
-
} catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Resolve readiness from the authenticated managed Agent connection. Controller
|
|
45
|
-
* booleans are inspection evidence, but cannot prove that the scoped Agent has
|
|
46
|
-
* completed its end-to-end Server sync.
|
|
47
|
-
*/
|
|
48
|
-
export function canForceReadyAfterSyncTimeout(agent) {
|
|
49
|
-
return !agent?.sandboxIdentity;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function isSandboxAgentReady(identity, connectedAgents = agents) {
|
|
53
|
-
if (!identity) return false;
|
|
54
|
-
for (const agent of connectedAgents.values()) {
|
|
55
|
-
const current = agent.sandboxIdentity;
|
|
56
|
-
if (agent.status === 'ready' && agent.isAlive && current
|
|
57
|
-
&& current.sandboxId === identity.sandboxId
|
|
58
|
-
&& agent.instanceId === identity.instanceId
|
|
59
|
-
&& current.generation === identity.generation
|
|
60
|
-
&& current.imageDigest === identity.imageDigest
|
|
61
|
-
&& agent.capabilities?.includes('managed-sandbox')) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { createServer } from 'https';
|
|
3
|
-
import { CONFIG } from './config.js';
|
|
4
|
-
import {
|
|
5
|
-
authenticateSandboxController,
|
|
6
|
-
handleSandboxHostAttestation
|
|
7
|
-
} from './routes/sandbox-routes.js';
|
|
8
|
-
|
|
9
|
-
export const SANDBOX_HOST_ATTESTATION_PATH = '/api/sandbox/hosts/attest';
|
|
10
|
-
|
|
11
|
-
function authenticatePeer(req, res, next, config) {
|
|
12
|
-
if (!authenticateSandboxController(req, config.controllerAttestationFingerprint)) {
|
|
13
|
-
return res.status(401).json({ code: 'SANDBOX_CONTROLLER_IDENTITY_REJECTED' });
|
|
14
|
-
}
|
|
15
|
-
return next();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function parseStrictJson(req, res, next) {
|
|
19
|
-
try {
|
|
20
|
-
const body = JSON.parse(req.body.toString('utf8'));
|
|
21
|
-
if (!body || typeof body !== 'object') {
|
|
22
|
-
return res.status(400).json({ code: 'SANDBOX_ATTESTATION_BODY_INVALID' });
|
|
23
|
-
}
|
|
24
|
-
req.body = body;
|
|
25
|
-
return next();
|
|
26
|
-
} catch {
|
|
27
|
-
return res.status(400).json({ code: 'SANDBOX_ATTESTATION_BODY_INVALID' });
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function bodyErrorHandler(err, req, res, next) {
|
|
32
|
-
if (!err) return next();
|
|
33
|
-
if (err.type === 'entity.too.large') {
|
|
34
|
-
return res.status(413).json({ code: 'SANDBOX_ATTESTATION_BODY_TOO_LARGE' });
|
|
35
|
-
}
|
|
36
|
-
return next(err);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Create the dedicated HTTPS server used only for Controller Host attestation.
|
|
41
|
-
* TLS chain authorization occurs before Express, while the route also pins the
|
|
42
|
-
* leaf certificate fingerprint before consuming the signed payload.
|
|
43
|
-
*
|
|
44
|
-
* @param {{ config?: object, handler?: Function }} options
|
|
45
|
-
* @returns {import('https').Server}
|
|
46
|
-
*/
|
|
47
|
-
export function createSandboxAttestationListener({
|
|
48
|
-
config = CONFIG.sandbox,
|
|
49
|
-
handler = handleSandboxHostAttestation
|
|
50
|
-
} = {}) {
|
|
51
|
-
const app = express();
|
|
52
|
-
app.disable('x-powered-by');
|
|
53
|
-
|
|
54
|
-
app.post(
|
|
55
|
-
SANDBOX_HOST_ATTESTATION_PATH,
|
|
56
|
-
(req, res, next) => authenticatePeer(req, res, next, config),
|
|
57
|
-
express.raw({ limit: config.hostAttestationBodyLimitBytes, type: () => true }),
|
|
58
|
-
parseStrictJson,
|
|
59
|
-
(req, res) => handler(req, res, config)
|
|
60
|
-
);
|
|
61
|
-
app.use(bodyErrorHandler);
|
|
62
|
-
|
|
63
|
-
const listener = createServer({
|
|
64
|
-
cert: config.hostAttestationServerCert,
|
|
65
|
-
key: config.hostAttestationServerKey,
|
|
66
|
-
ca: config.hostAttestationClientCa,
|
|
67
|
-
requestCert: true,
|
|
68
|
-
rejectUnauthorized: true,
|
|
69
|
-
minVersion: 'TLSv1.2'
|
|
70
|
-
}, app);
|
|
71
|
-
const sockets = new Set();
|
|
72
|
-
listener.on('connection', socket => {
|
|
73
|
-
sockets.add(socket);
|
|
74
|
-
socket.once('close', () => sockets.delete(socket));
|
|
75
|
-
});
|
|
76
|
-
listener.sandboxAttestationSockets = sockets;
|
|
77
|
-
listener.sandboxAttestationShutdownTimeoutMs = config.hostAttestationShutdownTimeoutMs;
|
|
78
|
-
return listener;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Start the attestation listener when Managed Sandbox is enabled.
|
|
83
|
-
* @param {{ config?: object, handler?: Function }} options
|
|
84
|
-
* @returns {Promise<import('https').Server|null>}
|
|
85
|
-
*/
|
|
86
|
-
export async function startSandboxAttestationListener({
|
|
87
|
-
config = CONFIG.sandbox,
|
|
88
|
-
handler = handleSandboxHostAttestation
|
|
89
|
-
} = {}) {
|
|
90
|
-
if (!config.enabled) return null;
|
|
91
|
-
const listener = createSandboxAttestationListener({ config, handler });
|
|
92
|
-
await new Promise((resolve, reject) => {
|
|
93
|
-
const onError = (err) => {
|
|
94
|
-
listener.off('listening', onListening);
|
|
95
|
-
reject(err);
|
|
96
|
-
};
|
|
97
|
-
const onListening = () => {
|
|
98
|
-
listener.off('error', onError);
|
|
99
|
-
resolve();
|
|
100
|
-
};
|
|
101
|
-
listener.once('error', onError);
|
|
102
|
-
listener.once('listening', onListening);
|
|
103
|
-
listener.listen(config.hostAttestationListenerPort, config.hostAttestationListenerHost);
|
|
104
|
-
});
|
|
105
|
-
return listener;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export async function closeSandboxAttestationListener(listener) {
|
|
109
|
-
if (!listener) return;
|
|
110
|
-
const sockets = listener.sandboxAttestationSockets || new Set();
|
|
111
|
-
const timeoutMs = listener.sandboxAttestationShutdownTimeoutMs || 1_000;
|
|
112
|
-
await new Promise((resolve, reject) => {
|
|
113
|
-
let settled = false;
|
|
114
|
-
const finish = err => {
|
|
115
|
-
if (settled) return;
|
|
116
|
-
settled = true;
|
|
117
|
-
clearTimeout(timer);
|
|
118
|
-
if (err && err.code !== 'ERR_SERVER_NOT_RUNNING') reject(err);
|
|
119
|
-
else resolve();
|
|
120
|
-
};
|
|
121
|
-
const timer = setTimeout(() => {
|
|
122
|
-
for (const socket of sockets) socket.destroy();
|
|
123
|
-
}, timeoutMs);
|
|
124
|
-
timer.unref?.();
|
|
125
|
-
listener.close(finish);
|
|
126
|
-
listener.closeIdleConnections?.();
|
|
127
|
-
});
|
|
128
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Existing Sandbox cleanup needs only the Controller trust and transport configuration.
|
|
3
|
-
* It deliberately does not depend on the product admission flag or creation-only settings.
|
|
4
|
-
* @param {object} config
|
|
5
|
-
* @returns {boolean}
|
|
6
|
-
*/
|
|
7
|
-
export function validateSandboxCleanupConfig(config) {
|
|
8
|
-
if (!config?.controllerUrl || !config.controllerToken
|
|
9
|
-
|| !config.operationSigningPrivateKey || !config.controllerResultPublicKey
|
|
10
|
-
|| !config.controllerHostId || !config.helperAttestationPublicKey
|
|
11
|
-
|| !config.controllerClientCert || !config.controllerClientKey
|
|
12
|
-
|| !config.controllerCaCert) return false;
|
|
13
|
-
try {
|
|
14
|
-
return new URL(config.controllerUrl).protocol === 'https:';
|
|
15
|
-
} catch {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Sandbox execution is allowed only through a fully configured dedicated HTTPS Controller.
|
|
22
|
-
* The current mixed-use Server Host is never an implicit execution target.
|
|
23
|
-
* @param {object} config
|
|
24
|
-
* @returns {boolean}
|
|
25
|
-
*/
|
|
26
|
-
export function validateSandboxDeploymentConfig(config) {
|
|
27
|
-
if (!config?.enabled || !validateSandboxCleanupConfig(config)
|
|
28
|
-
|| !config.bootstrapSigningKey
|
|
29
|
-
|| !config.hostAttestationKey || !config.controllerAttestationFingerprint
|
|
30
|
-
|| !config.hostAttestationListenerHost
|
|
31
|
-
|| !Number.isSafeInteger(config.hostAttestationListenerPort)
|
|
32
|
-
|| config.hostAttestationListenerPort <= 0
|
|
33
|
-
|| config.hostAttestationListenerPort > 65_535
|
|
34
|
-
|| !config.hostAttestationServerCert || !config.hostAttestationServerKey
|
|
35
|
-
|| !config.hostAttestationClientCa
|
|
36
|
-
|| !Number.isSafeInteger(config.hostAttestationBodyLimitBytes)
|
|
37
|
-
|| config.hostAttestationBodyLimitBytes <= 0
|
|
38
|
-
|| !config.imageDigest
|
|
39
|
-
|| !Number.isSafeInteger(config.hostMemoryReserveMiB)
|
|
40
|
-
|| config.hostMemoryReserveMiB <= 0) return false;
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
-
import db, { transaction } from './db/connection.js';
|
|
3
|
-
|
|
4
|
-
const HEALTH_FIELDS = Object.freeze([
|
|
5
|
-
'controllerHealthy',
|
|
6
|
-
'helperHealthy',
|
|
7
|
-
'runtimeHealthy',
|
|
8
|
-
'quotaHealthy',
|
|
9
|
-
'networkHealthy'
|
|
10
|
-
]);
|
|
11
|
-
|
|
12
|
-
export class SandboxHostAttestationError extends Error {
|
|
13
|
-
constructor(code) {
|
|
14
|
-
super(code);
|
|
15
|
-
this.code = code;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function canonicalPayload(attestation) {
|
|
20
|
-
return JSON.stringify({
|
|
21
|
-
hostId: attestation.hostId,
|
|
22
|
-
epoch: attestation.epoch,
|
|
23
|
-
nonce: attestation.nonce,
|
|
24
|
-
observedAt: attestation.observedAt,
|
|
25
|
-
imageDigest: attestation.imageDigest,
|
|
26
|
-
resources: attestation.resources,
|
|
27
|
-
checks: attestation.checks
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function validIdentifier(value, maxLength = 128) {
|
|
32
|
-
return typeof value === 'string' && value.length > 0 && value.length <= maxLength
|
|
33
|
-
&& /^[a-zA-Z0-9._:-]+$/.test(value);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function verifySignature(payload, signature, key) {
|
|
37
|
-
if (!key || typeof signature !== 'string') return false;
|
|
38
|
-
const expected = createHmac('sha256', key).update(payload).digest();
|
|
39
|
-
let actual;
|
|
40
|
-
try {
|
|
41
|
-
actual = Buffer.from(signature, 'base64url');
|
|
42
|
-
} catch {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function validateAttestation(attestation, config, now) {
|
|
49
|
-
if (!attestation || !validIdentifier(attestation.hostId) || !validIdentifier(attestation.epoch)
|
|
50
|
-
|| !validIdentifier(attestation.nonce, 192) || !validIdentifier(attestation.imageDigest, 192)) {
|
|
51
|
-
throw new SandboxHostAttestationError('SANDBOX_HOST_ATTESTATION_INVALID');
|
|
52
|
-
}
|
|
53
|
-
if (attestation.hostId !== config.controllerHostId
|
|
54
|
-
|| attestation.imageDigest !== config.imageDigest) {
|
|
55
|
-
throw new SandboxHostAttestationError('SANDBOX_HOST_ATTESTATION_SCOPE_MISMATCH');
|
|
56
|
-
}
|
|
57
|
-
const observedAt = Number(attestation.observedAt);
|
|
58
|
-
if (!Number.isSafeInteger(observedAt)
|
|
59
|
-
|| Math.abs(now - observedAt) > config.hostAttestationMaxSkewMs) {
|
|
60
|
-
throw new SandboxHostAttestationError('SANDBOX_HOST_ATTESTATION_STALE');
|
|
61
|
-
}
|
|
62
|
-
const resources = attestation.resources || {};
|
|
63
|
-
if (![resources.cpuMillis, resources.memoryMiB, resources.memoryAvailableMiB, resources.diskGiB]
|
|
64
|
-
.every(value => Number.isSafeInteger(value) && value > 0)
|
|
65
|
-
|| resources.memoryAvailableMiB > resources.memoryMiB) {
|
|
66
|
-
throw new SandboxHostAttestationError('SANDBOX_HOST_ATTESTATION_INVALID');
|
|
67
|
-
}
|
|
68
|
-
if (!verifySignature(canonicalPayload(attestation), attestation.signature, config.hostAttestationKey)) {
|
|
69
|
-
throw new SandboxHostAttestationError('SANDBOX_HOST_ATTESTATION_INVALID');
|
|
70
|
-
}
|
|
71
|
-
return Boolean(attestation.checks)
|
|
72
|
-
&& HEALTH_FIELDS.every(field => attestation.checks[field] === true);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function appendAudit(attestation, outcome, errorCode, now) {
|
|
76
|
-
db.prepare(`
|
|
77
|
-
INSERT INTO sandbox_host_audit_events (
|
|
78
|
-
host_id, epoch, event_type, outcome, error_code, created_at
|
|
79
|
-
) VALUES (?, ?, 'qualification_attested', ?, ?, ?)
|
|
80
|
-
`).run(
|
|
81
|
-
String(attestation?.hostId || 'invalid').slice(0, 128),
|
|
82
|
-
String(attestation?.epoch || 'invalid').slice(0, 128),
|
|
83
|
-
outcome,
|
|
84
|
-
errorCode,
|
|
85
|
-
now
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function registerSandboxHostAttestation(attestation, config, now = Date.now()) {
|
|
90
|
-
try {
|
|
91
|
-
const qualified = validateAttestation(attestation, config, now);
|
|
92
|
-
return transaction(() => {
|
|
93
|
-
const prior = db.prepare('SELECT nonce FROM sandbox_host_attestations WHERE nonce = ?')
|
|
94
|
-
.get(attestation.nonce);
|
|
95
|
-
if (prior) throw new SandboxHostAttestationError('SANDBOX_HOST_ATTESTATION_REPLAYED');
|
|
96
|
-
|
|
97
|
-
const latest = db.prepare(`
|
|
98
|
-
SELECT observed_at FROM sandbox_host_attestations
|
|
99
|
-
WHERE host_id = ? ORDER BY observed_at DESC LIMIT 1
|
|
100
|
-
`).get(attestation.hostId);
|
|
101
|
-
if (latest && Number(attestation.observedAt) <= latest.observed_at) {
|
|
102
|
-
throw new SandboxHostAttestationError('SANDBOX_HOST_ATTESTATION_OUT_OF_ORDER');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const priorEpoch = db.prepare(`
|
|
106
|
-
SELECT source_epoch, epoch FROM sandbox_host_epochs WHERE host_id = ?
|
|
107
|
-
`).get(attestation.hostId);
|
|
108
|
-
const allocatedEpoch = priorEpoch
|
|
109
|
-
? priorEpoch.epoch + Number(priorEpoch.source_epoch !== attestation.epoch)
|
|
110
|
-
: 1;
|
|
111
|
-
const { resources, checks } = attestation;
|
|
112
|
-
db.prepare(`
|
|
113
|
-
INSERT INTO sandbox_hosts (
|
|
114
|
-
id, epoch, qualified, controller_healthy, helper_healthy, runtime_healthy,
|
|
115
|
-
quota_healthy, network_healthy, image_digest, cpu_millis_total,
|
|
116
|
-
memory_mib_total, memory_mib_available, disk_gib_total, updated_at
|
|
117
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
118
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
119
|
-
epoch = excluded.epoch,
|
|
120
|
-
qualified = excluded.qualified,
|
|
121
|
-
controller_healthy = excluded.controller_healthy,
|
|
122
|
-
helper_healthy = excluded.helper_healthy,
|
|
123
|
-
runtime_healthy = excluded.runtime_healthy,
|
|
124
|
-
quota_healthy = excluded.quota_healthy,
|
|
125
|
-
network_healthy = excluded.network_healthy,
|
|
126
|
-
image_digest = excluded.image_digest,
|
|
127
|
-
cpu_millis_total = excluded.cpu_millis_total,
|
|
128
|
-
memory_mib_total = excluded.memory_mib_total,
|
|
129
|
-
memory_mib_available = excluded.memory_mib_available,
|
|
130
|
-
disk_gib_total = excluded.disk_gib_total,
|
|
131
|
-
updated_at = excluded.updated_at
|
|
132
|
-
`).run(
|
|
133
|
-
attestation.hostId, allocatedEpoch, Number(qualified),
|
|
134
|
-
Number(checks.controllerHealthy), Number(checks.helperHealthy),
|
|
135
|
-
Number(checks.runtimeHealthy), Number(checks.quotaHealthy), Number(checks.networkHealthy),
|
|
136
|
-
attestation.imageDigest, resources.cpuMillis, resources.memoryMiB,
|
|
137
|
-
resources.memoryAvailableMiB, resources.diskGiB, now
|
|
138
|
-
);
|
|
139
|
-
db.prepare(`
|
|
140
|
-
INSERT INTO sandbox_host_epochs
|
|
141
|
-
(host_id, source_epoch, epoch, activation_digest, activated_at, updated_at)
|
|
142
|
-
VALUES (?, ?, ?, NULL, NULL, ?)
|
|
143
|
-
ON CONFLICT(host_id) DO UPDATE SET
|
|
144
|
-
source_epoch = excluded.source_epoch,
|
|
145
|
-
epoch = excluded.epoch,
|
|
146
|
-
activation_digest = CASE
|
|
147
|
-
WHEN sandbox_host_epochs.epoch = excluded.epoch
|
|
148
|
-
THEN sandbox_host_epochs.activation_digest ELSE NULL END,
|
|
149
|
-
activated_at = CASE
|
|
150
|
-
WHEN sandbox_host_epochs.epoch = excluded.epoch
|
|
151
|
-
THEN sandbox_host_epochs.activated_at ELSE NULL END,
|
|
152
|
-
updated_at = excluded.updated_at
|
|
153
|
-
`).run(attestation.hostId, attestation.epoch, allocatedEpoch, now);
|
|
154
|
-
db.prepare(`
|
|
155
|
-
INSERT INTO sandbox_host_attestations (nonce, host_id, epoch, observed_at, created_at)
|
|
156
|
-
VALUES (?, ?, ?, ?, ?)
|
|
157
|
-
`).run(attestation.nonce, attestation.hostId, allocatedEpoch, attestation.observedAt, now);
|
|
158
|
-
appendAudit(
|
|
159
|
-
{ ...attestation, epoch: allocatedEpoch },
|
|
160
|
-
qualified ? 'accepted' : 'rejected',
|
|
161
|
-
qualified ? null : 'SANDBOX_HOST_NOT_QUALIFIED',
|
|
162
|
-
now
|
|
163
|
-
);
|
|
164
|
-
return { accepted: true, qualified, epoch: allocatedEpoch };
|
|
165
|
-
})();
|
|
166
|
-
} catch (error) {
|
|
167
|
-
const code = error instanceof SandboxHostAttestationError
|
|
168
|
-
? error.code
|
|
169
|
-
: 'SANDBOX_HOST_ATTESTATION_INVALID';
|
|
170
|
-
appendAudit(attestation, 'rejected', code, now);
|
|
171
|
-
throw error instanceof SandboxHostAttestationError
|
|
172
|
-
? error
|
|
173
|
-
: new SandboxHostAttestationError(code);
|
|
174
|
-
}
|
|
175
|
-
}
|