Fix bootstrap image preservation and ingest websocket close recursion
All checks were successful
deploy / deploy (push) Successful in 32s

Proof: npm test; npm run operator-dashboard:build; node --test test/near-intents-ws.test.mjs; bash -n scripts/deploy/bootstrap.sh; PYTHONPATH=. python3 test/bootstrap_script_static_test.py; PYTHONPATH=. python3 test/render_release_manifest_test.py; PYTHONPATH=. python3 test/repo_deployments_test.py; PYTHONPATH=. python3 test/ntfy_manifest_test.py; kubectl kustomize deploy/k8s/base.

Assumptions: bootstrap should preserve an existing deployed release image when refreshing app secrets; websocket close/error events may be reentrant in the Node runtime.

Still fake: Notification emissions are limited to credited deposits, completed withdrawals, and completed trades with durable inventory movement; generic alert notification policy remains disabled.
This commit is contained in:
philipp 2026-04-16 14:28:54 +02:00
parent c5a214ce06
commit ea0a7cbb4c
4 changed files with 75 additions and 2 deletions

View file

@ -196,8 +196,22 @@ kubectl -n "$PROJECT_NAMESPACE" create secret docker-registry "$PROJECT_REGISTRY
--docker-password="$REGISTRY_PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
current_release_image() {
kubectl -n "$PROJECT_NAMESPACE" get deploy operator-dashboard \
-o jsonpath='{.spec.template.spec.containers[0].image}' 2>/dev/null || true
}
APP_MANIFEST_IMAGE="${PROJECT_RELEASE_IMAGE:-$(current_release_image)}"
BOOTSTRAP_IMAGE="ghcr.io/example/unrip:bootstrap"
echo "applying app manifests"
kubectl apply -k "$ROOT_DIR/deploy/k8s/base"
if [[ -n "$APP_MANIFEST_IMAGE" && "$APP_MANIFEST_IMAGE" != "$BOOTSTRAP_IMAGE" ]]; then
kubectl kustomize "$ROOT_DIR/deploy/k8s/base" \
| python3 "$ROOT_DIR/scripts/deploy/render_release_manifest.py" --image "$APP_MANIFEST_IMAGE" \
| kubectl apply -f -
else
kubectl apply -k "$ROOT_DIR/deploy/k8s/base"
fi
echo "upserting Forgejo repo settings"
forgejo_args=()

View file

@ -43,6 +43,7 @@ export async function startNearIntentsWs({
let lastConnectedAt = null;
let lastDisconnectedAt = null;
let lastReconnectAt = null;
const closingSockets = new WeakSet();
function connect() {
if (closed) return;
@ -171,7 +172,8 @@ export async function startNearIntentsWs({
}
function closeSocket(ws) {
if (!ws || ws.readyState > WebSocket.OPEN) return;
if (!ws || ws.readyState > WebSocket.OPEN || closingSockets.has(ws)) return;
closingSockets.add(ws);
try {
ws.close();
} catch (error) {

View file

@ -0,0 +1,18 @@
import pathlib
import unittest
ROOT = pathlib.Path(__file__).resolve().parents[1]
class BootstrapScriptStaticTest(unittest.TestCase):
def test_bootstrap_renders_existing_release_image_instead_of_reapplying_placeholders(self):
source = (ROOT / 'scripts/deploy/bootstrap.sh').read_text()
self.assertIn('PROJECT_RELEASE_IMAGE', source)
self.assertIn('current_release_image', source)
self.assertIn('render_release_manifest.py', source)
self.assertIn('ghcr.io/example/unrip:bootstrap', source)
self.assertIn('kubectl kustomize "$ROOT_DIR/deploy/k8s/base"', source)
if __name__ == '__main__':
unittest.main()

View file

@ -23,6 +23,8 @@ function installMockWebSocket() {
this.readyState = MockWebSocket.CONNECTING;
this.sent = [];
this.listeners = new Map();
this.closeCalls = 0;
this.emitErrorDuringClose = false;
instances.push(this);
}
@ -37,7 +39,11 @@ function installMockWebSocket() {
}
close() {
this.closeCalls += 1;
if (this.readyState === MockWebSocket.CLOSED) return;
if (this.emitErrorDuringClose) {
this.emit('error', new Error('socket failed while closing'));
}
this.readyState = MockWebSocket.CLOSED;
this.emit('close', {});
}
@ -96,3 +102,36 @@ test('near intents ingest reconnects after websocket error before open without r
mock.restore();
}
});
test('near intents websocket close is reentrant-safe when close emits an error', async () => {
const mock = installMockWebSocket();
const producer = { sendJson: async () => {} };
const client = await startNearIntentsWs({
apiKey: 'api-key',
wsUrl: 'wss://relay.example/ws',
pairFilter: ['btc', 'eure'],
producer,
rawTopic: 'raw.near_intents.quote',
normalizedTopic: 'norm.swap_demand',
reconnectDelayMs: 1,
});
try {
assert.equal(mock.instances.length, 1);
mock.instances[0].open();
mock.instances[0].emitErrorDuringClose = true;
assert.doesNotThrow(() => {
mock.instances[0].error(new Error('network failed'));
});
await delay(10);
assert.equal(mock.instances[0].closeCalls, 1);
assert.equal(client.getState().connected, false);
assert.ok(client.getState().reconnect_count >= 2);
assert.ok(mock.instances.length >= 2);
} finally {
client.close();
mock.restore();
}
});