phase 5: wasm core, GalaxyClient skeleton, Connect-Web stubs

Compile `ui/core` to WebAssembly via TinyGo (903 KB) and expose four
canonical-bytes / signature-verification functions on
`globalThis.galaxyCore` from `ui/wasm/main.go`. The TypeScript-side
`Core` interface plus a `WasmCore` adapter (browser + JSDOM loader)
bridge those into a typed shape, and a `GalaxyClient` skeleton wires
`Core.signRequest` → injected `Signer` → typed Connect client →
`Core.verifyPayloadHash` / `verifyResponse`.

Wire `ui/buf.gen.yaml` against the local
`@bufbuild/protoc-gen-es` v2 binary (devDependency) so the codegen
step does not depend on the buf.build BSR. Vitest covers the bridge
end-to-end: per-method WasmCore tests under JSDOM, byte-for-byte
canon parity against the gateway fixtures committed in Phase 3, and
a `GalaxyClient` orchestration test using
`createRouterTransport`. The committed `core.wasm` snapshot tracks
TinyGo output so contributors run `make wasm` only when `ui/core/`
changes; CI consumes the snapshot directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-07 12:58:37 +02:00
parent cd61868881
commit fbc0260720
25 changed files with 7284 additions and 36 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,262 @@
// @generated by protoc-gen-es v2.12.0 with parameter "target=ts"
// @generated from file galaxy/gateway/v1/edge_gateway.proto (package galaxy.gateway.v1, syntax proto3)
/* eslint-disable */
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
import { file_buf_validate_validate } from "../../../buf/validate/validate_pb";
import type { Message } from "@bufbuild/protobuf";
/**
* Describes the file galaxy/gateway/v1/edge_gateway.proto.
*/
export const file_galaxy_gateway_v1_edge_gateway: GenFile = /*@__PURE__*/
fileDesc("CiRnYWxheHkvZ2F0ZXdheS92MS9lZGdlX2dhdGV3YXkucHJvdG8SEWdhbGF4eS5nYXRld2F5LnYxIqYCChVFeGVjdXRlQ29tbWFuZFJlcXVlc3QSIQoQcHJvdG9jb2xfdmVyc2lvbhgBIAEoCUIHukgEcgIQARIiChFkZXZpY2Vfc2Vzc2lvbl9pZBgCIAEoCUIHukgEcgIQARIdCgxtZXNzYWdlX3R5cGUYAyABKAlCB7pIBHICEAESHQoMdGltZXN0YW1wX21zGAQgASgDQge6SAQiAiAAEhsKCnJlcXVlc3RfaWQYBSABKAlCB7pIBHICEAESHgoNcGF5bG9hZF9ieXRlcxgGIAEoDEIHukgEegIQARIdCgxwYXlsb2FkX2hhc2gYByABKAxCB7pIBHoCEAESGgoJc2lnbmF0dXJlGAggASgMQge6SAR6AhABEhAKCHRyYWNlX2lkGAkgASgJIrEBChZFeGVjdXRlQ29tbWFuZFJlc3BvbnNlEhgKEHByb3RvY29sX3ZlcnNpb24YASABKAkSEgoKcmVxdWVzdF9pZBgCIAEoCRIUCgx0aW1lc3RhbXBfbXMYAyABKAMSEwoLcmVzdWx0X2NvZGUYBCABKAkSFQoNcGF5bG9hZF9ieXRlcxgFIAEoDBIUCgxwYXlsb2FkX2hhc2gYBiABKAwSEQoJc2lnbmF0dXJlGAcgASgMIp4CChZTdWJzY3JpYmVFdmVudHNSZXF1ZXN0EiEKEHByb3RvY29sX3ZlcnNpb24YASABKAlCB7pIBHICEAESIgoRZGV2aWNlX3Nlc3Npb25faWQYAiABKAlCB7pIBHICEAESHQoMbWVzc2FnZV90eXBlGAMgASgJQge6SARyAhABEh0KDHRpbWVzdGFtcF9tcxgEIAEoA0IHukgEIgIgABIbCgpyZXF1ZXN0X2lkGAUgASgJQge6SARyAhABEh0KDHBheWxvYWRfaGFzaBgGIAEoDEIHukgEegIQARIaCglzaWduYXR1cmUYByABKAxCB7pIBHoCEAESFQoNcGF5bG9hZF9ieXRlcxgIIAEoDBIQCgh0cmFjZV9pZBgJIAEoCSKwAQoMR2F0ZXdheUV2ZW50EhIKCmV2ZW50X3R5cGUYASABKAkSEAoIZXZlbnRfaWQYAiABKAkSFAoMdGltZXN0YW1wX21zGAMgASgDEhUKDXBheWxvYWRfYnl0ZXMYBCABKAwSFAoMcGF5bG9hZF9oYXNoGAUgASgMEhEKCXNpZ25hdHVyZRgGIAEoDBISCgpyZXF1ZXN0X2lkGAcgASgJEhAKCHRyYWNlX2lkGAggASgJMtUBCgtFZGdlR2F0ZXdheRJlCg5FeGVjdXRlQ29tbWFuZBIoLmdhbGF4eS5nYXRld2F5LnYxLkV4ZWN1dGVDb21tYW5kUmVxdWVzdBopLmdhbGF4eS5nYXRld2F5LnYxLkV4ZWN1dGVDb21tYW5kUmVzcG9uc2USXwoPU3Vic2NyaWJlRXZlbnRzEikuZ2FsYXh5LmdhdGV3YXkudjEuU3Vic2NyaWJlRXZlbnRzUmVxdWVzdBofLmdhbGF4eS5nYXRld2F5LnYxLkdhdGV3YXlFdmVudDABQjJaMGdhbGF4eS9nYXRld2F5L3Byb3RvL2dhbGF4eS9nYXRld2F5L3YxO2dhdGV3YXl2MWIGcHJvdG8z", [file_buf_validate_validate]);
/**
* @generated from message galaxy.gateway.v1.ExecuteCommandRequest
*/
export type ExecuteCommandRequest = Message<"galaxy.gateway.v1.ExecuteCommandRequest"> & {
/**
* protocol_version identifies the request envelope version. The gateway
* accepts only the literal "v1" after required-field validation succeeds.
*
* @generated from field: string protocol_version = 1;
*/
protocolVersion: string;
/**
* @generated from field: string device_session_id = 2;
*/
deviceSessionId: string;
/**
* @generated from field: string message_type = 3;
*/
messageType: string;
/**
* @generated from field: int64 timestamp_ms = 4;
*/
timestampMs: bigint;
/**
* @generated from field: string request_id = 5;
*/
requestId: string;
/**
* @generated from field: bytes payload_bytes = 6;
*/
payloadBytes: Uint8Array;
/**
* payload_hash is the raw 32-byte SHA-256 digest of payload_bytes.
*
* @generated from field: bytes payload_hash = 7;
*/
payloadHash: Uint8Array;
/**
* @generated from field: bytes signature = 8;
*/
signature: Uint8Array;
/**
* @generated from field: string trace_id = 9;
*/
traceId: string;
};
/**
* Describes the message galaxy.gateway.v1.ExecuteCommandRequest.
* Use `create(ExecuteCommandRequestSchema)` to create a new message.
*/
export const ExecuteCommandRequestSchema: GenMessage<ExecuteCommandRequest> = /*@__PURE__*/
messageDesc(file_galaxy_gateway_v1_edge_gateway, 0);
/**
* @generated from message galaxy.gateway.v1.ExecuteCommandResponse
*/
export type ExecuteCommandResponse = Message<"galaxy.gateway.v1.ExecuteCommandResponse"> & {
/**
* @generated from field: string protocol_version = 1;
*/
protocolVersion: string;
/**
* @generated from field: string request_id = 2;
*/
requestId: string;
/**
* @generated from field: int64 timestamp_ms = 3;
*/
timestampMs: bigint;
/**
* @generated from field: string result_code = 4;
*/
resultCode: string;
/**
* @generated from field: bytes payload_bytes = 5;
*/
payloadBytes: Uint8Array;
/**
* @generated from field: bytes payload_hash = 6;
*/
payloadHash: Uint8Array;
/**
* @generated from field: bytes signature = 7;
*/
signature: Uint8Array;
};
/**
* Describes the message galaxy.gateway.v1.ExecuteCommandResponse.
* Use `create(ExecuteCommandResponseSchema)` to create a new message.
*/
export const ExecuteCommandResponseSchema: GenMessage<ExecuteCommandResponse> = /*@__PURE__*/
messageDesc(file_galaxy_gateway_v1_edge_gateway, 1);
/**
* @generated from message galaxy.gateway.v1.SubscribeEventsRequest
*/
export type SubscribeEventsRequest = Message<"galaxy.gateway.v1.SubscribeEventsRequest"> & {
/**
* protocol_version identifies the request envelope version. The gateway
* accepts only the literal "v1" after required-field validation succeeds.
*
* @generated from field: string protocol_version = 1;
*/
protocolVersion: string;
/**
* @generated from field: string device_session_id = 2;
*/
deviceSessionId: string;
/**
* @generated from field: string message_type = 3;
*/
messageType: string;
/**
* @generated from field: int64 timestamp_ms = 4;
*/
timestampMs: bigint;
/**
* @generated from field: string request_id = 5;
*/
requestId: string;
/**
* payload_hash is the raw 32-byte SHA-256 digest of payload_bytes. Empty
* payloads must use the SHA-256 digest of the empty byte slice.
*
* @generated from field: bytes payload_hash = 6;
*/
payloadHash: Uint8Array;
/**
* @generated from field: bytes signature = 7;
*/
signature: Uint8Array;
/**
* @generated from field: bytes payload_bytes = 8;
*/
payloadBytes: Uint8Array;
/**
* @generated from field: string trace_id = 9;
*/
traceId: string;
};
/**
* Describes the message galaxy.gateway.v1.SubscribeEventsRequest.
* Use `create(SubscribeEventsRequestSchema)` to create a new message.
*/
export const SubscribeEventsRequestSchema: GenMessage<SubscribeEventsRequest> = /*@__PURE__*/
messageDesc(file_galaxy_gateway_v1_edge_gateway, 2);
/**
* @generated from message galaxy.gateway.v1.GatewayEvent
*/
export type GatewayEvent = Message<"galaxy.gateway.v1.GatewayEvent"> & {
/**
* @generated from field: string event_type = 1;
*/
eventType: string;
/**
* @generated from field: string event_id = 2;
*/
eventId: string;
/**
* @generated from field: int64 timestamp_ms = 3;
*/
timestampMs: bigint;
/**
* @generated from field: bytes payload_bytes = 4;
*/
payloadBytes: Uint8Array;
/**
* @generated from field: bytes payload_hash = 5;
*/
payloadHash: Uint8Array;
/**
* @generated from field: bytes signature = 6;
*/
signature: Uint8Array;
/**
* @generated from field: string request_id = 7;
*/
requestId: string;
/**
* @generated from field: string trace_id = 8;
*/
traceId: string;
};
/**
* Describes the message galaxy.gateway.v1.GatewayEvent.
* Use `create(GatewayEventSchema)` to create a new message.
*/
export const GatewayEventSchema: GenMessage<GatewayEvent> = /*@__PURE__*/
messageDesc(file_galaxy_gateway_v1_edge_gateway, 3);
/**
* @generated from service galaxy.gateway.v1.EdgeGateway
*/
export const EdgeGateway: GenService<{
/**
* @generated from rpc galaxy.gateway.v1.EdgeGateway.ExecuteCommand
*/
executeCommand: {
methodKind: "unary";
input: typeof ExecuteCommandRequestSchema;
output: typeof ExecuteCommandResponseSchema;
},
/**
* @generated from rpc galaxy.gateway.v1.EdgeGateway.SubscribeEvents
*/
subscribeEvents: {
methodKind: "server_streaming";
input: typeof SubscribeEventsRequestSchema;
output: typeof GatewayEventSchema;
},
}> = /*@__PURE__*/
serviceDesc(file_galaxy_gateway_v1_edge_gateway, 0);