Compare commits

..

32 Commits

Author SHA1 Message Date
Quixos Subtree Publisher cd2b04dc8c Publish camino-package-runtime from 29c36195235aeb4b60042e7ec4df0b0d2bcfff33 2026-09-17 01:14:23 +00:00
Timothy J. Aveni fc838413c5 Release updated TODO template dependencies 2026-09-16 18:14:23 -07:00
Timothy J. Aveni 5bb8ee876d Add checked React field bindings and Web Studio factories
Replace opaque props with checked generic presentation contracts and lazy typed
interface references. Generate readonly/writable field APIs and component checks.

Preserve CRDT editing through explicit resolved getter/setter contracts, binding-
fenced delta RPCs, native watches and replica-aware field adapters. Custom setters
retain semantic writes; storage snapshots never grant write authority. Cover
concurrent edits, lost acknowledgements, readonly contracts and authorization.

Add receiver-free static factory dispatch, state-field binding shorthand, and
conformance-based creation. Migrate TODO, editable scaffolds and authoring guides.
Verify language/codegen, SDK, RPC, browser lifecycle, local scaffolds, production
browser bundling and CRDT persistence with temporary PostgreSQL.
2026-09-16 16:49:09 -07:00
Timothy J. Aveni 0e183516c0 Revert generated-file renaming; retain formatter exclusions 2026-09-15 20:13:10 -07:00
Timothy J. Aveni 2319b1f637 Mark first-party generated files with .gen; preserve third-party conventions 2026-09-15 17:57:46 -07:00
Timothy J. Aveni 0854822923 Add lightweight correctness linting and resolve formatting conflicts 2026-09-15 15:41:24 -07:00
Timothy J. Aveni 0d3c013c07 Format authored monorepo code with pinned language formatters 2026-09-15 15:41:24 -07:00
Timothy J. Aveni bee4452852 Repair workspace authoring: typed RPC inputs, automatic preservation, and template releases
Use checked input contracts and protobuf JSON for CLI roundtrips; generate Web Studio platform inputs; reject invalid constructors before allocation and filter Createable eligibility.

Preserve compatible storage without no-op migrations, report activation readiness, and validate migration coverage before maintenance. Follow authored package declarations during scaffold refresh and queue source capture.

Add a real local TODO check/activate/create/place/edit acceptance, publish updated protocol and SDK dependencies, and make verified template default selection explicit.
2026-09-14 16:04:59 -07:00
Quixos Subtree Publisher d3d050d9cb Publish camino-package-runtime from a9e94c961430415bcea302c47181d44ec7e2344c 2026-09-14 22:58:04 +00:00
Timothy J. Aveni e8a175504a Make workspace authoring transitions and typed invocation coherent 2026-09-14 15:58:04 -07:00
Timothy J. Aveni 3dd4e9a1ad Migrate TODO implementations to generated candidate bindings 2026-09-13 20:28:50 -07:00
Quixos Subtree Publisher 62ae43ee40 Publish camino-package-runtime from 22bb3d02264980d74de65c34bbbcb81764dc65c0 2026-09-14 03:28:50 +00:00
Quixos Subtree Publisher 110e41b268 Publish camino-package-runtime from e25eee6ce4f13702b2454a9354bc79a29eb2e4a1 2026-09-14 03:01:53 +00:00
Timothy J. Aveni 2b7035fe81 Migrate TODO implementations to generated candidate bindings 2026-09-13 20:01:53 -07:00
Timothy J. Aveni 8480674212 Fix supervised credentials and declare typed RPC records for canvas placement 2026-09-13 16:38:55 -07:00
Quixos Subtree Publisher 79fbee2b32 Publish camino-package-runtime from 61cb9aa5a580c2846d426464228c7d1082b7021a 2026-09-13 23:38:55 +00:00
Timothy J. Aveni a2fe7e4f5e Document public mirrors and align standalone SDK with current protocol 2026-09-13 16:03:03 -07:00
Quixos Subtree Publisher 652ac8e53e Publish camino-package-runtime from 351b7d74d316380481d8fcf212e03d6ec777a2f3 2026-09-13 23:03:03 +00:00
Timothy J. Aveni 0907f97803 Retain supervised package failures and final activation diagnostics 2026-09-13 12:23:18 -07:00
Timothy J. Aveni ce6ae8f662 Implement workspace evolution, migrations, and runtime continuity
Enable evolution by default for source-backed workspaces. Add stable
conformance ownership, semantic-major review, candidate typechecking,
and durable fenced cutover with explicit migrations and forward recovery.

Independently supervise package runtimes so unchanged resource owners keep
their processes and connections across cutover. Add scoped invocation
authority, resource sessions, and typed callback rebinding.

Wire opaque object references through generated bindings and RPCs. Add
canonical relationship sets, keyed maps, and ordered lists with scoped
transactional mutations, revision checks, and inverse consistency. Support
planned cascade deletion, protection, tombstones, and lifecycle foundations.

Add journaled structural edits, package/function/migration scaffolding,
managed repository creation, and resumable bottom-up dependency pin
publication. Document lifetime boundaries, revision pinning, prototype
compatibility policy, commands, and deferred work.

Validate with 210 tests, user-systemd process/connection continuity,
generated-package TypeScript checks, and Nix host/protocol checks.
TTL handoff, physical reclamation, general multi-step migrations, and
root-systemd migration isolation acceptance remain deferred.
2026-09-10 18:29:25 -07:00
Timothy J. Aveni 549c4539d5 Generate typed QX bindings and add source editing tools 2026-09-08 15:42:20 -07:00
timothy fa9e77e75c Build workspace agent, capability graph, and versioned cutovers 2026-09-06 11:12:04 -07:00
Quixos Subtree Publisher a998690446 Publish camino-package-runtime from d24af92a8e059a5ce3a15fc8caaa9b346bfa8375 2026-09-06 00:50:46 +00:00
timothy 46c8950a75 Add the workspace coding agent, launch gate, and composable locks
- run a singleton Codex App Server adapter in each dev workspace
- gate Web Studio with a reloadable fragment-token to HttpOnly-session exchange
- add ChatGPT device login and streamed chat to Web Studio
- isolate authoring access in a declarative NixOS service account
- split Web Studio canvas, object, panel, and model concerns
- check generated Quixos protocol clients for drift
- resolve same-repository quixos.lock fragments deterministically
- preserve the collaborative canvas gesture fixes
2026-09-05 17:50:46 -07:00
timothy 2de080c09f Make package-runtime publication verify generated distribution 2026-09-04 21:28:56 -07:00
Quixos Subtree Publisher c15a8a51fa Publish camino-package-runtime from fa363fa2f4e83d0996c1229ab1ecf9e0541d5d4f 2026-09-05 04:28:56 +00:00
timothy d83f82c90f Pin runtimes to persisted component protocol 2026-09-04 21:07:14 -07:00
Quixos Subtree Publisher b7b4a63155 Publish camino-package-runtime from e77d2eb0eb5ed034fb5e20a3600412eda4e3c73d 2026-09-05 04:07:14 +00:00
timothy 04614be2c9 Introduce persisted Web Studio component capabilities 2026-09-04 21:04:43 -07:00
timothy 16b344c0ad Introduce repository-backed capability workspaces
- define and validate the capability and resource-lock languages
- provision workspace source repositories through Central Gitea
- build package runtimes from pinned standalone sources
- replace legacy schema compilation with workspace persistence plans
- add stable optimistic registers and Automerge CRDT documents
- modernize TypeScript/Nix package builds and runtime activation readiness
2026-09-04 19:08:10 -07:00
Quixos Subtree Publisher 385a5bd595 Publish camino-package-runtime from 321d5fa6c8b7619c1dc9cc4976318f246e645812 2026-09-05 01:30:55 +00:00
timothy 9771b0803e Host workspace repositories on Central Gitea 2026-09-04 18:30:55 -07:00
53 changed files with 4386 additions and 563 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git",
"sourceCommit": "ce0dd80f56df35bf3db1be01236a949a704cb4c2",
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
"sourceCommit": "29c36195235aeb4b60042e7ec4df0b0d2bcfff33",
"sourcePath": "quixos-instance/packages/camino-package-runtime",
"exportName": "camino-package-runtime",
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git"
+84
View File
@@ -0,0 +1,84 @@
import { type Value } from "./camino/api_pb.js";
import { liveValue, type RuntimeHandler, type DerivedHandler } from "./index.js";
export type { QxObjectRef } from "./references.js";
declare const watchBrand: unique symbol;
export type QxWatchHandle = string & {
readonly [watchBrand]: true;
};
export type MessageBinding<T> = {
encode(value: T): Value;
decode(value: Value): T;
};
export type QxLiveValue = ReturnType<typeof liveValue>;
export type BindingValue<B> = B extends MessageBinding<infer T> ? T : never;
export type QxHandler<C, O> = (context: C) => O | Promise<O>;
export type QxDerived<C, O> = {
kind: "derived";
get: QxHandler<C, O>;
};
export type QxSession<C> = {
id: string;
run<T>(work: (context: C) => Promise<T>): Promise<T>;
close(): Promise<void>;
};
export type QxContextLifecycle<C> = {
signal?: AbortSignal;
openSession?: () => Promise<QxSession<C>>;
};
export declare const qxDerived: <C, O>(get: QxHandler<C, O>) => QxDerived<C, O>;
/** Versioned binding ABI. This mirrors the language-neutral value IR. */
export type QxValueType = {
kind: "builtin";
name: "unit" | "watch-handle";
} | {
kind: "scalar";
name: string;
} | {
kind: "message";
descriptorId: string;
} | {
kind: "record";
fields: Record<string, QxValueType>;
} | {
kind: "object-ref";
expectation: unknown;
} | {
kind: "optional" | "list";
value: QxValueType;
};
export type QxOperationSpec = {
id: string;
inputType: QxValueType;
outputType: QxValueType;
};
export type QxPortSpec = {
kind: "state";
id: string;
valueType: QxValueType;
primitives: string[];
} | {
kind: "edge";
id: string;
primitives: string[];
} | {
kind: "interface";
id: string;
operations: Record<string, QxOperationSpec>;
} | {
kind: "constructor";
id: string;
inputType: QxValueType;
};
export type QxHandlerSpec = {
receiver?: "none";
inputType: QxValueType;
outputType: QxValueType;
eventType?: QxValueType;
ports: Record<string, QxPortSpec>;
};
export type QxMessages = Record<string, MessageBinding<any>>;
export declare const decodeQxValue: (type: QxValueType, value: Value | undefined, messages: QxMessages) => any;
export declare const encodeQxValue: (type: QxValueType, value: any, messages: QxMessages) => Value;
/** The sole unchecked cast connects generated contracts to the dynamic RPC runtime. */
export declare const bindQxHandler: <C, O>(spec: QxHandlerSpec, handler: QxHandler<C, O> | QxDerived<C, O>, messages: QxMessages) => RuntimeHandler | DerivedHandler;
//# sourceMappingURL=bindings.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
+205
View File
@@ -0,0 +1,205 @@
import { create } from "@bufbuild/protobuf";
import { ValueSchema, ObjectValueSchema } from "./camino/api_pb.js";
import { derived, jsToProtoValue, liveValue, protoValueToJs, } from "./index.js";
import { assertReferenceFree, referenceToWire } from "./references.js";
export const qxDerived = (get) => ({ kind: "derived", get });
// Conversion belongs at the binding boundary. It does not add orchestrator validation.
export const decodeQxValue = (type, value, messages) => {
if (type.kind === "builtin" && type.name === "unit")
return null;
if (type.kind === "optional" && !value)
return null;
if (!value)
throw new Error("Missing QX wire value");
if (type.kind === "record") {
if (value.kind.case !== "objectValue")
throw new Error("Expected QX record");
const fields = value.kind.value.fields;
if (Object.keys(fields).some((name) => !Object.hasOwn(type.fields, name)))
throw new Error("Unexpected QX record field");
return Object.fromEntries(Object.entries(type.fields).map(([name, field]) => [name, decodeQxValue(field, fields[name], messages)]));
}
if (type.kind === "optional")
return value.kind.case === "nullValue" ? null : decodeQxValue(type.value, value, messages);
if (type.kind === "list") {
if (value.kind.case !== "listValue")
throw new Error("Expected QX list");
return value.kind.value.values.map((entry) => decodeQxValue(type.value, entry, messages));
}
if (type.kind === "message") {
assertReferenceFree(protoValueToJs(value));
const decoded = requireMessage(messages, type.descriptorId).decode(value);
assertReferenceFree(decoded);
return decoded;
}
if (type.kind === "object-ref") {
if (value.kind.case !== "refValue")
throw new Error("Expected a declared RPC object reference");
return protoValueToJs(value);
}
if (value.kind.case === "refValue")
throw new Error("Reference supplied to a non-reference value");
if (type.kind === "scalar") {
if (type.name === "int64" || type.name === "uint64") {
if (value.kind.case !== "integerValue")
throw new Error("Expected QX integer");
return BigInt(value.kind.value);
}
if (type.name === "bytes") {
if (value.kind.case !== "bytesValue")
throw new Error("Expected QX bytes");
return value.kind.value;
}
}
return protoValueToJs(value);
};
const requireMessage = (messages, id) => {
const binding = messages[id];
if (!binding)
throw new Error(`Missing message binding ${id}`);
return binding;
};
export const encodeQxValue = (type, value, messages) => {
if (type.kind === "builtin" && type.name === "unit")
return jsToProtoValue(null);
if (type.kind === "record") {
if (!value || typeof value !== "object" || Array.isArray(value))
throw new Error("Expected QX record");
if (Object.keys(value).some((name) => !Object.hasOwn(type.fields, name)))
throw new Error("Unexpected QX record field");
const fields = Object.fromEntries(Object.entries(type.fields).map(([name, field]) => {
if (!Object.hasOwn(value, name) && field.kind !== "optional")
throw new Error(`Missing QX record field ${name}`);
return [name, encodeQxValue(field, value[name] ?? (field.kind === "optional" ? null : value[name]), messages)];
}));
return create(ValueSchema, { kind: { case: "objectValue", value: create(ObjectValueSchema, { fields }) } });
}
if (type.kind === "optional")
return value === null ? jsToProtoValue(null) : encodeQxValue(type.value, value, messages);
if (type.kind === "list")
return jsToProtoValue(value.map((entry) => liveValue(encodeQxValue(type.value, entry, messages))));
if (type.kind === "object-ref") {
referenceToWire(value);
return jsToProtoValue(value);
}
assertReferenceFree(value);
if (type.kind === "message") {
const encoded = requireMessage(messages, type.descriptorId).encode(value);
assertReferenceFree(protoValueToJs(encoded));
return encoded;
}
return jsToProtoValue(value);
};
const inputValue = (context, type) => {
if (type.kind === "message" || type.kind === "record")
return create(ValueSchema, {
kind: { case: "objectValue", value: create(ObjectValueSchema, { fields: context.inputProto }) },
});
return context.inputProto.value;
};
const inputFields = (type, value, messages) => {
if (type.kind === "builtin" && type.name === "unit")
return {};
const encoded = encodeQxValue(type, value, messages);
if (type.kind === "message" || type.kind === "record") {
if (encoded.kind.case !== "objectValue")
throw new Error("Message inputs must encode an object value");
return Object.fromEntries(Object.entries(encoded.kind.value.fields).map(([key, entry]) => [key, liveValue(entry)]));
}
return { value: liveValue(encoded) };
};
/** The sole unchecked cast connects generated contracts to the dynamic RPC runtime. */
export const bindQxHandler = (spec, handler, messages) => {
const bindContext = (raw) => {
const ports = Object.fromEntries(Object.entries(spec.ports).map(([name, port]) => {
switch (port.kind) {
case "state": {
const state = raw.state(port.id);
return [
name,
{
...(port.primitives.includes("read")
? {
get: async () => decodeQxValue(port.valueType, (await state.live()).$quixosValue, messages),
live: () => state.live(),
}
: {}),
...(port.primitives.includes("write")
? {
set: async (value) => state.set(liveValue(encodeQxValue(port.valueType, value, messages))),
}
: {}),
},
];
}
case "edge": {
const edge = raw.edge(port.id);
return [
name,
{
...Object.fromEntries(port.primitives.map((primitive) => [
primitive,
edge[primitive],
])),
...(port.primitives.includes("resolve") ? { collection: edge.collection } : {}),
...(port.primitives.includes("resolve") &&
port.primitives.includes("connect") &&
port.primitives.includes("disconnect")
? { replace: edge.replace }
: {}),
},
];
}
case "interface": {
const target = raw.interface(port.id);
return [
name,
{
objectId: target.objectId,
live: Object.fromEntries(Object.entries(port.operations).map(([name, operation]) => [
name,
(input) => target.live(operation.id, inputFields(operation.inputType, input, messages)),
])),
...Object.fromEntries(Object.entries(port.operations).map(([name, operation]) => [
name,
async (input) => decodeQxValue(operation.outputType, (await target.live(operation.id, inputFields(operation.inputType, input, messages)))
.$quixosValue, messages),
])),
},
];
}
case "constructor":
return [
name,
{
construct: (input) => raw.constructor(port.id).construct(inputFields(port.inputType, input, messages)),
},
];
}
}));
return {
...(spec.receiver === "none" ? {} : { objectId: raw.objectId }),
signal: raw.signal,
...(spec.receiver !== "none" && raw.openSession
? {
openSession: async () => {
const session = await raw.openSession();
return {
id: session.id,
close: () => session.close(),
run: (work) => session.run((next) => work(bindContext(next))),
};
},
}
: {}),
input: decodeQxValue(spec.inputType, inputValue(raw, spec.inputType), messages),
ports,
};
};
const execute = async (raw) => {
const context = bindContext(raw);
const value = await (typeof handler === "function" ? handler(context) : handler.get(context));
return liveValue(encodeQxValue(spec.eventType ?? spec.outputType, value, messages));
};
return typeof handler === "function" ? execute : derived(execute);
};
+110
View File
@@ -104,6 +104,14 @@ export type StateValueSource = Message<"camino.StateValueSource"> & {
* @generated from field: uint64 revision = 5;
*/
revision: bigint;
/**
* CRDT-backed state is still read as its materialized Value. The snapshot
* travels with the writable source identity so a client can retain and
* advance a local replica without exposing the document to package code.
*
* @generated from field: camino.CrdtValue crdt_snapshot = 6;
*/
crdtSnapshot?: CrdtValue | undefined;
};
/**
* Describes the message camino.StateValueSource.
@@ -534,6 +542,78 @@ export type DisconnectEdgeResponse = Message<"camino.DisconnectEdgeResponse"> &
* Use `create(DisconnectEdgeResponseSchema)` to create a new message.
*/
export declare const DisconnectEdgeResponseSchema: GenMessage<DisconnectEdgeResponse>;
/**
* @generated from message camino.CollectionEntry
*/
export type CollectionEntry = Message<"camino.CollectionEntry"> & {
/**
* Existing entry identity to preserve; empty allocates a new canonical edge.
*
* @generated from field: string edge_id = 1;
*/
edgeId: string;
/**
* @generated from field: string target_object_id = 2;
*/
targetObjectId: string;
/**
* @generated from field: camino.Value key = 3;
*/
key?: Value | undefined;
};
/**
* Describes the message camino.CollectionEntry.
* Use `create(CollectionEntrySchema)` to create a new message.
*/
export declare const CollectionEntrySchema: GenMessage<CollectionEntry>;
/**
* @generated from message camino.ReadCollectionResponse
*/
export type ReadCollectionResponse = Message<"camino.ReadCollectionResponse"> & {
/**
* @generated from field: uint64 revision = 1;
*/
revision: bigint;
/**
* @generated from field: repeated camino.CollectionEntry entries = 2;
*/
entries: CollectionEntry[];
};
/**
* Describes the message camino.ReadCollectionResponse.
* Use `create(ReadCollectionResponseSchema)` to create a new message.
*/
export declare const ReadCollectionResponseSchema: GenMessage<ReadCollectionResponse>;
/**
* @generated from message camino.ReplaceCollectionRequest
*/
export type ReplaceCollectionRequest = Message<"camino.ReplaceCollectionRequest"> & {
/**
* @generated from field: string object_id = 1;
*/
objectId: string;
/**
* @generated from field: string edge_type_id = 2;
*/
edgeTypeId: string;
/**
* @generated from field: string projection_id = 3;
*/
projectionId: string;
/**
* @generated from field: uint64 expected_revision = 4;
*/
expectedRevision: bigint;
/**
* @generated from field: repeated camino.CollectionEntry entries = 5;
*/
entries: CollectionEntry[];
};
/**
* Describes the message camino.ReplaceCollectionRequest.
* Use `create(ReplaceCollectionRequestSchema)` to create a new message.
*/
export declare const ReplaceCollectionRequestSchema: GenMessage<ReplaceCollectionRequest>;
/**
* @generated from message camino.ListOpsRequest
*/
@@ -578,6 +658,12 @@ export type WatchObjectRequest = Message<"camino.WatchObjectRequest"> & {
* @generated from field: bool include_snapshot = 3;
*/
includeSnapshot: boolean;
/**
* Managed runtimes must watch only their injected attachments.
*
* @generated from field: repeated string attachment_ids = 4;
*/
attachmentIds: string[];
};
/**
* Describes the message camino.WatchObjectRequest.
@@ -688,6 +774,14 @@ export type CaminoEdge = Message<"camino.CaminoEdge"> & {
* @generated from field: string created_at = 9;
*/
createdAt: string;
/**
* @generated from field: string first_key_json = 10;
*/
firstKeyJson: string;
/**
* @generated from field: string second_key_json = 11;
*/
secondKeyJson: string;
};
/**
* Describes the message camino.CaminoEdge.
@@ -816,6 +910,22 @@ export declare const CaminoService: GenService<{
input: typeof DisconnectEdgeRequestSchema;
output: typeof DisconnectEdgeResponseSchema;
};
/**
* @generated from rpc camino.CaminoService.ReadCollection
*/
readCollection: {
methodKind: "unary";
input: typeof ResolveEdgeRequestSchema;
output: typeof ReadCollectionResponseSchema;
};
/**
* @generated from rpc camino.CaminoService.ReplaceCollection
*/
replaceCollection: {
methodKind: "unary";
input: typeof ReplaceCollectionRequestSchema;
output: typeof ReadCollectionResponseSchema;
};
/**
* @generated from rpc camino.CaminoService.ListOps
*/
+1 -1
View File
File diff suppressed because one or more lines are too long
+23 -8
View File
File diff suppressed because one or more lines are too long
+34
View File
@@ -34,6 +34,10 @@ export type AtomConformance = Message<"camino.AtomConformance"> & {
* @generated from field: string interface_revision_id = 2;
*/
interfaceRevisionId: string;
/**
* @generated from field: string conformance_id = 3;
*/
conformanceId: string;
};
/**
* Describes the message camino.AtomConformance.
@@ -68,6 +72,10 @@ export type StateAttachment = Message<"camino.StateAttachment"> & {
* @generated from field: string default_value_json = 6;
*/
defaultValueJson: string;
/**
* @generated from field: string owner_conformance_id = 7;
*/
ownerConformanceId: string;
};
/**
* Describes the message camino.StateAttachment.
@@ -127,6 +135,28 @@ export type EdgeEndpoint = Message<"camino.EdgeEndpoint"> & {
* @generated from field: bool ordered = 5;
*/
ordered: boolean;
/**
* Empty means restrict. Direction is the endpoint being deleted.
*
* @generated from field: string on_delete = 6;
*/
onDelete: string;
/**
* @generated from field: bool retain_other = 7;
*/
retainOther: boolean;
/**
* Empty for sets/lists, otherwise string, boolean, or int64 map keys.
*
* @generated from field: string key_type = 8;
*/
keyType: string;
/**
* Explicit read-only dependency injection traversal, not mutation authority.
*
* @generated from field: bool public_traversal = 9;
*/
publicTraversal: boolean;
};
/**
* Describes the message camino.EdgeEndpoint.
@@ -153,6 +183,10 @@ export type EdgeAttachment = Message<"camino.EdgeAttachment"> & {
* @generated from field: camino.EdgeEndpoint second = 4;
*/
second?: EdgeEndpoint | undefined;
/**
* @generated from field: string owner_conformance_id = 5;
*/
ownerConformanceId: string;
};
/**
* Describes the message camino.EdgeAttachment.
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"schema_pb.d.ts","sourceRoot":"","sources":["../../src/camino/schema_pb.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"schema_pb.d.ts","sourceRoot":"","sources":["../../src/camino/schema_pb.ts"],"names":[],"mappings":"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"}
+1 -1
View File
@@ -5,7 +5,7 @@ import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
/**
* Describes the file camino/schema.proto.
*/
export const file_camino_schema = /*@__PURE__*/ fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiQQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJIqQBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIqYBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCCKHAQoORWRnZUF0dGFjaG1lbnQSFAoMZWRnZV90eXBlX2lkGAEgASgJEhQKDGRpc3BsYXlfbmFtZRgCIAEoCRIjCgVmaXJzdBgDIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSJAoGc2Vjb25kGAQgASgLMhQuY2FtaW5vLkVkZ2VFbmRwb2ludCLsAQoPUGVyc2lzdGVuY2VQbGFuEhQKDHdvcmtzcGFjZV9pZBgBIAEoCRIdChV3b3Jrc3BhY2VfcmV2aXNpb25faWQYAiABKAkSJQoFYXRvbXMYAyADKAsyFi5jYW1pbm8uQXRvbURlZmluaXRpb24SLQoMY29uZm9ybWFuY2VzGAQgAygLMhcuY2FtaW5vLkF0b21Db25mb3JtYW5jZRInCgZzdGF0ZXMYBSADKAsyFy5jYW1pbm8uU3RhdGVBdHRhY2htZW50EiUKBWVkZ2VzGAYgAygLMhYuY2FtaW5vLkVkZ2VBdHRhY2htZW50KmgKC0NhcmRpbmFsaXR5EhsKF0NBUkRJTkFMSVRZX1VOU1BFQ0lGSUVEEAASEAoMT1BUSU9OQUxfT05FEAESDwoLRVhBQ1RMWV9PTkUQAhIICgRNQU5ZEAMSDwoLTUFOWV9VTklRVUUQBGIGcHJvdG8z");
export const file_camino_schema = /*@__PURE__*/ fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiWQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJIsIBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYByABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIvsBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCBIRCglvbl9kZWxldGUYBiABKAkSFAoMcmV0YWluX290aGVyGAcgASgIEhAKCGtleV90eXBlGAggASgJEhgKEHB1YmxpY190cmF2ZXJzYWwYCSABKAgipQEKDkVkZ2VBdHRhY2htZW50EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSIwoFZmlyc3QYAyABKAsyFC5jYW1pbm8uRWRnZUVuZHBvaW50EiQKBnNlY29uZBgEIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYBSABKAki7AEKD1BlcnNpc3RlbmNlUGxhbhIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEiUKBWF0b21zGAMgAygLMhYuY2FtaW5vLkF0b21EZWZpbml0aW9uEi0KDGNvbmZvcm1hbmNlcxgEIAMoCzIXLmNhbWluby5BdG9tQ29uZm9ybWFuY2USJwoGc3RhdGVzGAUgAygLMhcuY2FtaW5vLlN0YXRlQXR0YWNobWVudBIlCgVlZGdlcxgGIAMoCzIWLmNhbWluby5FZGdlQXR0YWNobWVudCpoCgtDYXJkaW5hbGl0eRIbChdDQVJESU5BTElUWV9VTlNQRUNJRklFRBAAEhAKDE9QVElPTkFMX09ORRABEg8KC0VYQUNUTFlfT05FEAISCAoETUFOWRADEg8KC01BTllfVU5JUVVFEARiBnByb3RvMw");
/**
* Describes the message camino.AtomDefinition.
* Use `create(AtomDefinitionSchema)` to create a new message.
+36 -8
View File
@@ -1,4 +1,8 @@
import http from "node:http";
import { type QxObjectRef } from "./references.js";
export * from "./bindings.js";
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
export { createMigrationContext, migrationObjectId, serveMigration, type MigrationContext, type MigrationInput, type MigrationOutput, type MigrationEdge, } from "./migration.js";
import { type Client, type ConnectRouter } from "@connectrpc/connect";
import { CaminoService, type Value } from "./camino/api_pb.js";
import { OrchestratorRuntime } from "./quixos/orch_pb.js";
@@ -14,9 +18,7 @@ export type RuntimeDependency = {
attachmentId: string;
projectionId: string;
};
export declare const objectRef: (objectId: string) => {
$quixosRef: string;
};
export declare const objectRef: (reference: QxObjectRef) => QxObjectRef<string>;
export declare const liveValue: (value: Value) => {
$quixosValue: Value;
};
@@ -34,20 +36,37 @@ export type StatePort<T = unknown> = {
export type EdgePort = {
edgeTypeId: string;
projectionId: string;
resolve(): Promise<string[]>;
connect(targetObjectId: string): Promise<void>;
resolve(): Promise<QxObjectRef[]>;
connect(target: QxObjectRef): Promise<void>;
disconnect(target: QxObjectRef): Promise<void>;
collection(): Promise<RelationshipCollection>;
replace(entries: RelationshipEntry[], expectedRevision: bigint): Promise<RelationshipCollection>;
};
export type RelationshipEntry<T extends QxObjectRef = QxObjectRef> = {
edgeId?: string;
target: T;
key?: string | boolean | bigint;
};
export type RelationshipCollection<T extends QxObjectRef = QxObjectRef> = {
revision: bigint;
entries: RelationshipEntry<T>[];
};
export type InterfacePort = {
objectId: QxObjectRef;
interfaceRevisionId: string;
invoke(operationId: string, input?: Record<string, unknown>): Promise<unknown>;
live(operationId: string, input?: Record<string, unknown>): Promise<ReturnType<typeof liveValue>>;
};
export type ConstructorPort = {
atomId: string;
construct(input?: Record<string, unknown>): Promise<string>;
construct(input?: Record<string, unknown>): Promise<QxObjectRef>;
};
export type RuntimePort = StatePort | EdgePort | InterfacePort | ConstructorPort;
export type RuntimeContext = {
objectId: string;
/** Cooperative cancellation. Completion is acknowledged only after the handler returns. */
signal?: AbortSignal;
openSession?: () => Promise<RuntimeSession>;
objectId: QxObjectRef;
input: Record<string, unknown>;
inputProto: Record<string, Value>;
ports: ReadonlyMap<string, RuntimePort>;
@@ -56,6 +75,16 @@ export type RuntimeContext = {
interface(portId: string): InterfacePort;
constructor(portId: string): ConstructorPort;
};
export type RuntimeSession = {
id: string;
run<T>(work: (context: RuntimeContext) => Promise<T>): Promise<T>;
/** Close the external resource first, then close its host retention/session. */
close(): Promise<void>;
};
export declare class RuntimeAuthorityError extends Error {
readonly retryable: boolean;
constructor(message: string);
}
export declare const createRuntimeContext: (camino: CaminoClient, orch: OrchClient, request: RuntimeRequest) => RuntimeContext;
export type RuntimeHandler = (context: RuntimeContext) => unknown | Promise<unknown>;
export type DerivedHandler = {
@@ -78,5 +107,4 @@ type RuntimeRequest = {
input: Record<string, Value>;
dependencies: import("./quixos/refs_pb.js").InjectedDependency[];
};
export {};
//# sourceMappingURL=index.d.ts.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
+437 -155
View File
@@ -1,6 +1,12 @@
import http from "node:http";
import { readFileSync } from "node:fs";
import { createInvocationRegistry } from "./invocations.js";
import { isObjectReference, referenceFromWire, referenceToWire, assertReferenceFree, } from "./references.js";
export * from "./bindings.js";
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "node:crypto";
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
export { createMigrationContext, migrationObjectId, serveMigration, } from "./migration.js";
import { create, equals } from "@bufbuild/protobuf";
import { Code, ConnectError, createClient } from "@connectrpc/connect";
import { connectNodeAdapter, createConnectTransport } from "@connectrpc/connect-node";
@@ -21,11 +27,20 @@ const recordDependency = async (dependency) => {
const bytesToBase64 = (value) => Buffer.from(value).toString("base64");
const base64ToBytes = (value) => Buffer.from(value, "base64");
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
const isWrappedValue = (value) => isRecord(value) && "$quixosValue" in value &&
isRecord(value.$quixosValue) && value.$quixosValue.$typeName === "camino.Value";
export const objectRef = (objectId) => ({ $quixosRef: objectId });
const isWrappedValue = (value) => isRecord(value) &&
"$quixosValue" in value &&
isRecord(value.$quixosValue) &&
value.$quixosValue.$typeName === "camino.Value";
export const objectRef = (reference) => {
referenceToWire(reference);
return reference;
};
export const liveValue = (value) => ({ $quixosValue: value });
export const jsToProtoValue = (value) => {
if (isObjectReference(value))
return create(ValueSchema, {
kind: { case: "refValue", value: create(RefValueSchema, { objectId: referenceToWire(value) }) },
});
if (isWrappedValue(value))
return value.$quixosValue;
if (value === null || value === undefined) {
@@ -46,71 +61,90 @@ export const jsToProtoValue = (value) => {
kind: { case: "listValue", value: create(ListValueSchema, { values: value.map(jsToProtoValue) }) },
});
}
if (isRecord(value) && typeof value.$quixosRef === "string") {
if (isRecord(value) && "$quixosRef" in value)
throw new Error("Raw ID wrappers are not object references");
if (isRecord(value) && typeof value.$quixosCrdtType === "string" && typeof value.$quixosCrdtPayload === "string") {
return create(ValueSchema, {
kind: { case: "refValue", value: create(RefValueSchema, { objectId: value.$quixosRef }) },
});
}
if (isRecord(value) && typeof value.$quixosCrdtType === "string" &&
typeof value.$quixosCrdtPayload === "string") {
return create(ValueSchema, {
kind: { case: "crdtValue", value: create(CrdtValueSchema, {
kind: {
case: "crdtValue",
value: create(CrdtValueSchema, {
type: value.$quixosCrdtType,
encoding: typeof value.$quixosCrdtEncoding === "string" ? value.$quixosCrdtEncoding : "base64",
payload: base64ToBytes(value.$quixosCrdtPayload),
}) },
}),
},
});
}
if (!isRecord(value))
throw new Error(`Unsupported runtime value ${typeof value}`);
return create(ValueSchema, {
kind: { case: "objectValue", value: create(ObjectValueSchema, {
kind: {
case: "objectValue",
value: create(ObjectValueSchema, {
fields: Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsToProtoValue(entry)])),
}) },
}),
},
});
};
export const protoValueToJs = (value) => {
switch (value?.kind.case) {
case "nullValue":
case undefined: return null;
case undefined:
return null;
case "boolValue":
case "numberValue":
case "stringValue":
case "integerValue": return value.kind.value;
case "bytesValue": return bytesToBase64(value.kind.value);
case "refValue": return value.kind.value.objectId;
case "listValue": return value.kind.value.values.map(protoValueToJs);
case "objectValue": return Object.fromEntries(Object.entries(value.kind.value.fields).map(([key, entry]) => [key, protoValueToJs(entry)]));
case "crdtValue": return {
$quixosCrdtType: value.kind.value.type,
$quixosCrdtEncoding: value.kind.value.encoding,
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
};
case "integerValue":
return value.kind.value;
case "bytesValue":
return bytesToBase64(value.kind.value);
case "refValue":
return referenceFromWire(value.kind.value.objectId);
case "listValue":
return value.kind.value.values.map(protoValueToJs);
case "objectValue":
return Object.fromEntries(Object.entries(value.kind.value.fields).map(([key, entry]) => [key, protoValueToJs(entry)]));
case "crdtValue":
return {
$quixosCrdtType: value.kind.value.type,
$quixosCrdtEncoding: value.kind.value.encoding,
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
};
}
};
export const protoFieldsToJs = (fields) => Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, protoValueToJs(value)]));
const targetForEdge = (edge, projectionId) => edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId;
export class RuntimeAuthorityError extends Error {
retryable;
constructor(message) {
super(message);
this.name = "RuntimeAuthorityError";
this.retryable = /WORKSPACE_FENCED|STALE_EPOCH/.test(message);
}
}
const targetForEdge = (edge, projectionId) => (edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId);
export const createRuntimeContext = (camino, orch, request) => {
const ports = new Map();
for (const dependency of request.dependencies) {
switch (dependency.binding.case) {
case "stateSlotId": {
const slotId = dependency.binding.value;
const dependencyObjectId = dependency.objectId || request.objectId;
const state = {
slotId,
async get() {
await recordDependency({ kind: "state", objectId: request.objectId, attachmentId: slotId });
return protoValueToJs((await camino.readState({ objectId: request.objectId, slotId })).value);
await recordDependency({ kind: "state", objectId: dependencyObjectId, attachmentId: slotId });
return protoValueToJs((await camino.readState({ objectId: dependencyObjectId, slotId })).value);
},
async live() {
await recordDependency({ kind: "state", objectId: request.objectId, attachmentId: slotId });
const value = await camino.readState({ objectId: request.objectId, slotId });
await recordDependency({ kind: "state", objectId: dependencyObjectId, attachmentId: slotId });
const value = await camino.readState({ objectId: dependencyObjectId, slotId });
if (!value.value)
throw new Error(`State ${slotId} returned no value`);
return liveValue(value.value);
},
async set(value) {
await camino.writeState({ objectId: request.objectId, slotId, value: jsToProtoValue(value) });
assertReferenceFree(isWrappedValue(value) ? protoValueToJs(value.$quixosValue) : value);
await camino.writeState({ objectId: dependencyObjectId, slotId, value: jsToProtoValue(value) });
},
};
ports.set(dependency.portId, state);
@@ -118,34 +152,116 @@ export const createRuntimeContext = (camino, orch, request) => {
}
case "edge": {
const { edgeTypeId, projectionId } = dependency.binding.value;
const dependencyObjectId = dependency.objectId || request.objectId;
const collectionResult = (response) => ({
revision: response.revision,
entries: response.entries.map((entry) => ({
edgeId: entry.edgeId,
target: referenceFromWire(entry.targetObjectId),
...(entry.key
? {
key: entry.key.kind.case === "integerValue"
? BigInt(entry.key.kind.value)
: protoValueToJs(entry.key),
}
: {}),
})),
});
const edge = {
edgeTypeId,
projectionId,
async resolve() {
await recordDependency({ kind: "edge", objectId: request.objectId, attachmentId: edgeTypeId, projectionId });
const result = await camino.resolveEdge({ objectId: request.objectId, edgeTypeId, projectionId });
return result.edges.map((entry) => targetForEdge(entry, projectionId));
async collection() {
await recordDependency({
kind: "edge",
objectId: dependencyObjectId,
attachmentId: edgeTypeId,
projectionId,
});
return collectionResult(await camino.readCollection({ objectId: dependencyObjectId, edgeTypeId, projectionId }));
},
async connect(targetObjectId) {
await camino.connectEdge({ objectId: request.objectId, edgeTypeId, projectionId, targetObjectId });
async replace(entries, expectedRevision) {
return collectionResult(await camino.replaceCollection({
objectId: dependencyObjectId,
edgeTypeId,
projectionId,
expectedRevision,
entries: entries.map((entry) => {
assertReferenceFree(entry.key);
return {
edgeId: entry.edgeId ?? "",
targetObjectId: referenceToWire(entry.target),
key: entry.key === undefined ? undefined : jsToProtoValue(entry.key),
};
}),
}));
},
async resolve() {
await recordDependency({
kind: "edge",
objectId: dependencyObjectId,
attachmentId: edgeTypeId,
projectionId,
});
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
return result.edges.map((entry) => referenceFromWire(targetForEdge(entry, projectionId)));
},
async connect(target) {
const targetObjectId = referenceToWire(target);
await camino.connectEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId, targetObjectId });
},
async disconnect(target) {
const targetObjectId = referenceToWire(target);
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
for (const entry of result.edges) {
if (targetForEdge(entry, projectionId) === targetObjectId)
await camino.disconnectEdge({ edgeId: entry.id });
}
},
};
ports.set(dependency.portId, edge);
break;
}
case "receiverInterfaceRevisionId": {
case "interfaceRevisionId": {
const interfaceRevisionId = dependency.binding.value;
const dependencyObjectId = dependency.objectId || request.objectId;
const invoke = async (operationId, input) => {
const response = await orch.invokeCapability({
capability: create(CapabilityRefSchema, { interfaceRevisionId, operationId }),
objectId: dependencyObjectId,
input: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsToProtoValue(value)])),
});
if (!response.ok)
throw new Error(response.error || `Capability ${operationId} failed`);
for (const dependency of response.dependencies) {
if (dependency.kind === "state") {
await recordDependency({
kind: "state",
objectId: dependency.objectId,
attachmentId: dependency.attachmentId,
});
}
else if (dependency.kind === "edge") {
await recordDependency({
kind: "edge",
objectId: dependency.objectId,
attachmentId: dependency.attachmentId,
projectionId: dependency.projectionId,
});
}
}
return response.result;
};
const capability = {
objectId: referenceFromWire(dependencyObjectId),
interfaceRevisionId,
async invoke(operationId, input = {}) {
const response = await orch.invokeCapability({
capability: create(CapabilityRefSchema, { interfaceRevisionId, operationId }),
objectId: request.objectId,
input: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsToProtoValue(value)])),
});
if (!response.ok)
throw new Error(response.error || `Capability ${operationId} failed`);
return protoValueToJs(response.result);
return protoValueToJs(await invoke(operationId, input));
},
async live(operationId, input = {}) {
const value = await invoke(operationId, input);
if (!value)
throw new Error(`Capability ${operationId} returned no value`);
return liveValue(value);
},
};
ports.set(dependency.portId, capability);
@@ -162,7 +278,7 @@ export const createRuntimeContext = (camino, orch, request) => {
});
if (!response.object)
throw new Error(`Constructor ${atomId} returned no object`);
return response.object.id;
return referenceFromWire(response.object.id);
},
};
ports.set(dependency.portId, constructor);
@@ -176,7 +292,9 @@ export const createRuntimeContext = (camino, orch, request) => {
return port;
};
return {
objectId: request.objectId,
get objectId() {
return referenceFromWire(request.objectId);
},
input: protoFieldsToJs(request.input),
inputProto: request.input,
ports,
@@ -200,9 +318,14 @@ const protoDependencies = (dependencies) => dependencies.map((entry) => create(D
projectionId: entry.kind === "edge" ? entry.projectionId : "",
}));
export const createPackageRuntimeRoutes = (config) => {
const invocations = createInvocationRegistry();
const headers = {};
if (process.env.CAMINO_RUNTIME_AUTH_TOKEN) {
headers["x-camino-runtime-token"] = process.env.CAMINO_RUNTIME_AUTH_TOKEN;
const processToken = process.env.CAMINO_RUNTIME_AUTH_TOKEN ??
(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE
? readFileSync(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE, "utf8").trim()
: "");
if (processToken) {
headers["x-camino-runtime-token"] = processToken;
}
else if (process.env.CAMINO_RUNTIME_AUTH_REQUIRED === "1") {
throw new Error("CAMINO_RUNTIME_AUTH_TOKEN is required");
@@ -210,33 +333,163 @@ export const createPackageRuntimeRoutes = (config) => {
const camino = createClient(CaminoService, createConnectTransport({
baseUrl: config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310",
httpVersion: "1.1",
interceptors: headers["x-camino-runtime-token"] ? [
(next) => async (request) => {
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]);
return await next(request);
},
] : [],
interceptors: headers["x-camino-runtime-token"]
? [
(next) => async (request) => {
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]);
return await next(request);
},
]
: [],
}));
const orch = createClient(OrchestratorRuntime, createConnectTransport({
baseUrl: config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311",
httpVersion: "1.1",
}));
const authenticateInstance = (header) => {
if (!process.env.QUIXOS_RUNTIME_INSTANCE_ID)
return; // standalone development ABI
const supplied = Buffer.from(header.get("x-quixos-instance-token") ?? "");
const expected = Buffer.from(processToken);
if (!expected.length || supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) {
throw new ConnectError("Invalid runtime instance credential", Code.Unauthenticated);
}
};
const clientsFor = (request) => {
const context = request.context;
if (process.env.QUIXOS_RUNTIME_INSTANCE_ID &&
(!context?.grant || context.instanceId !== process.env.QUIXOS_RUNTIME_INSTANCE_ID || !context.workspaceEpoch)) {
throw new ConnectError("Managed invocation requires an exact instance and epoch grant", Code.Unauthenticated);
}
if (!context?.grant)
return { camino, orch };
const transport = (url) => createConnectTransport({
baseUrl: url,
httpVersion: "1.1",
interceptors: [
(next) => async (call) => {
call.header.set("x-quixos-invocation-grant", context.grant);
call.header.set("x-camino-runtime-token", processToken);
return next(call);
},
],
});
return {
camino: createClient(CaminoService, transport(config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310")),
orch: createClient(OrchestratorRuntime, transport(config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311")),
};
};
const runtimeControl = async (operation, input) => {
const response = await fetch(`${config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310"}/__runtime/${operation}`, {
method: "POST",
headers: { "content-type": "application/json", "x-camino-runtime-token": processToken },
body: JSON.stringify(input),
signal: AbortSignal.timeout(10_000),
});
const value = (await response.json());
if (!response.ok)
throw new RuntimeAuthorityError(value.error ?? "Runtime authority request failed");
return value;
};
const attachSessions = (runtimeContext, request) => {
if (!request.context?.grant || !request.context.ownerConformanceId)
return;
runtimeContext.openSession = async () => {
const ownerId = request.context.ownerConformanceId;
const registration = {
grant: request.context.grant,
objectId: request.objectId,
ownerId,
sessionId: `session:${randomBytes(16).toString("hex")}`,
token: randomBytes(32).toString("base64url"),
};
const register = () => runtimeControl("register-session", registration);
const registered = await register().catch((error) => {
// Retry a transport/lost-response failure with exactly the same identity.
// Admission/authority errors are definitive and must not be retried here.
if (error instanceof RuntimeAuthorityError)
throw error;
return register();
});
let closed = false;
return {
id: registered.sessionId,
async run(work) {
if (closed)
throw new RuntimeAuthorityError("SESSION_CLOSED");
// Acquisition happens before user code. A fence failure can be retried
// by the caller without replaying a side-effecting callback.
const grant = await runtimeControl("acquire-session", registered);
const execution = invocations.begin(grant.invocationId);
const sessionRequest = {
...request,
context: { grant: grant.grant, instanceId: grant.instanceId, workspaceEpoch: grant.epoch },
};
const clients = clientsFor(sessionRequest);
const context = createRuntimeContext(clients.camino, clients.orch, sessionRequest);
context.signal = execution.signal;
try {
return await work(context);
}
finally {
execution.finish();
await runtimeControl("complete-invocation", { invocationId: grant.invocationId }).catch((error) => console.error("Session completion will be reconciled by the host", error));
}
},
async close() {
await runtimeControl("close-session", registered);
closed = true;
},
};
};
};
return (router) => router.service(PackageRuntime, {
handshake: () => create(HandshakeResponseSchema, {
handshake: (request) => create(HandshakeResponseSchema, {
packageRevisionId: config.packageRevisionId,
runtimeProtocolVersion: "quixos-capabilities-v1",
exportIds: Object.keys(config.exports),
capabilities: ["invocation-completion-v1", "instance-authentication-v1", "epoch-grants-v1"],
instanceId: process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
authenticationProof: request.nonce && processToken
? createHmac("sha256", processToken)
.update(JSON.stringify([
request.nonce,
process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
config.packageRevisionId,
]))
.digest("hex")
: "",
}),
invoke: async (request) => {
getInvocationStatus: (request, context) => {
authenticateInstance(context.requestHeader);
return invocations.status(request.invocationId);
},
cancelInvocation: (request, context) => {
authenticateInstance(context.requestHeader);
return invocations.cancel(request.invocationId);
},
invoke: async (request, context) => {
authenticateInstance(context.requestHeader);
const { camino, orch } = clientsFor(request);
const exportId = request.export?.exportId;
const handler = exportId ? config.exports[exportId] : undefined;
if (!handler)
throw new ConnectError(`Unknown export ${exportId ?? ""}`, Code.NotFound);
const execution = invocations.begin(request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()));
try {
const result = await evaluate(handler, createRuntimeContext(camino, orch, request));
return create(InvokeResponseSchema, { ok: true, result: result.value });
const runtimeContext = createRuntimeContext(camino, orch, request);
attachSessions(runtimeContext, request);
runtimeContext.signal = AbortSignal.any([context.signal, execution.signal]);
const result = await evaluate(handler, runtimeContext);
execution.finish();
return create(InvokeResponseSchema, {
ok: true,
result: result.value,
dependencies: protoDependencies(result.dependencies),
});
}
catch (error) {
execution.finish(true);
return create(InvokeResponseSchema, {
ok: false,
error: error instanceof Error ? error.message : String(error),
@@ -244,114 +497,143 @@ export const createPackageRuntimeRoutes = (config) => {
}
},
watch: async function* (request, context) {
authenticateInstance(context.requestHeader);
const { camino, orch } = clientsFor(request);
const exportId = request.export?.exportId;
const handler = exportId ? config.exports[exportId] : undefined;
if (!handler || !isDerived(handler)) {
throw new ConnectError(`Export ${exportId ?? ""} is not derived`, Code.FailedPrecondition);
}
const watchId = `watch:${randomUUID()}`;
const runtimeContext = createRuntimeContext(camino, orch, request);
const subscriptions = new Map();
const establishing = new Map();
const ensureSubscription = async (dependency) => {
const key = dependencyKey(dependency);
if (subscriptions.has(key))
return;
const pending = establishing.get(key);
if (pending)
return await pending;
const establish = (async () => {
const controller = new AbortController();
const stream = camino.watchObject({ objectId: dependency.objectId, includeSnapshot: true }, { signal: controller.signal })[Symbol.asyncIterator]();
const execution = invocations.begin(request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()));
const signal = AbortSignal.any([context.signal, execution.signal]);
try {
const watchId = `watch:${randomUUID()}`;
const runtimeContext = createRuntimeContext(camino, orch, request);
attachSessions(runtimeContext, request);
runtimeContext.signal = signal;
const subscriptions = new Map();
const establishing = new Map();
let subscriptionEpoch = 0;
const ensureSubscription = async (dependency) => {
const key = dependencyKey(dependency);
if (subscriptions.has(key))
return;
const pending = establishing.get(key);
if (pending)
return await pending;
const establish = (async () => {
const controller = new AbortController();
const stream = camino
.watchObject({
objectId: dependency.objectId,
includeSnapshot: true,
attachmentIds: request.context?.grant ? [dependency.attachmentId] : [],
}, { signal: controller.signal })[Symbol.asyncIterator]();
try {
// Camino subscribes before producing the snapshot, so once this
// resolves the following state/edge read cannot race the stream.
const snapshot = await stream.next();
if (snapshot.done)
throw new Error(`Dependency stream ${key} ended during setup`);
const waitNext = () => stream.next().then((result) => ({ key, done: Boolean(result.done) }), (error) => ({ key, done: true, error }));
const subscription = {
dependency,
controller,
waitNext,
next: Promise.resolve({ key, done: false }),
};
subscription.next = waitNext();
subscriptions.set(key, subscription);
subscriptionEpoch += 1;
}
catch (error) {
controller.abort();
throw error;
}
})();
establishing.set(key, establish);
try {
// Camino subscribes before producing the snapshot, so once this
// resolves the following state/edge read cannot race the stream.
const snapshot = await stream.next();
if (snapshot.done)
throw new Error(`Dependency stream ${key} ended during setup`);
const waitNext = () => stream.next().then((result) => ({ key, done: Boolean(result.done) }), (error) => ({ key, done: true, error }));
const subscription = {
dependency,
controller,
waitNext,
next: Promise.resolve({ key, done: false }),
};
subscription.next = waitNext();
subscriptions.set(key, subscription);
await establish;
}
catch (error) {
controller.abort();
throw error;
finally {
establishing.delete(key);
}
})();
establishing.set(key, establish);
};
const abortAll = () => {
for (const subscription of subscriptions.values()) {
subscription.controller.abort();
}
};
signal.addEventListener("abort", abortAll, { once: true });
const evaluateWithStableSubscriptions = async () => {
// A direct state/edge port records its dependency before reading it,
// but a nested interface invocation can only report its transitive
// dependencies after that invocation returns. Once a new dependency
// stream is established, evaluate again so every read contributing to
// the emitted value happened after its stream became live.
for (let pass = 0; pass < 32; pass += 1) {
const before = subscriptionEpoch;
const result = await evaluate(handler, runtimeContext, ensureSubscription);
if (subscriptionEpoch === before)
return result;
}
throw new Error("Derived dependency discovery did not stabilize after 32 passes");
};
try {
await establish;
let current = await evaluateWithStableSubscriptions();
yield create(WatchEventSchema, {
watchId,
value: current.value,
dependencies: protoDependencies(current.dependencies),
initial: true,
});
const abort = new Promise((resolve) => {
if (signal.aborted)
resolve("abort");
else
signal.addEventListener("abort", () => resolve("abort"), { once: true });
});
while (!signal.aborted) {
if (subscriptions.size === 0) {
await abort;
break;
}
const outcome = await Promise.race([...[...subscriptions.values()].map((entry) => entry.next), abort]);
if (outcome === "abort")
break;
const subscription = subscriptions.get(outcome.key);
if (!subscription)
continue;
if (outcome.error)
throw outcome.error;
if (outcome.done)
throw new Error(`Dependency stream ${outcome.key} ended unexpectedly`);
subscription.next = subscription.waitNext();
const updated = await evaluateWithStableSubscriptions();
const active = new Set(updated.dependencies.map(dependencyKey));
for (const [key, entry] of subscriptions) {
if (!active.has(key)) {
entry.controller.abort();
subscriptions.delete(key);
}
}
if (!equals(ValueSchema, current.value, updated.value)) {
yield create(WatchEventSchema, {
watchId,
value: updated.value,
dependencies: protoDependencies(updated.dependencies),
});
}
current = updated;
}
}
finally {
establishing.delete(key);
}
};
const abortAll = () => {
for (const subscription of subscriptions.values()) {
subscription.controller.abort();
}
};
context.signal.addEventListener("abort", abortAll, { once: true });
let current = await evaluate(handler, runtimeContext, ensureSubscription);
yield create(WatchEventSchema, {
watchId,
value: current.value,
dependencies: protoDependencies(current.dependencies),
initial: true,
});
const abort = new Promise((resolve) => {
if (context.signal.aborted)
resolve("abort");
else
context.signal.addEventListener("abort", () => resolve("abort"), { once: true });
});
try {
while (!context.signal.aborted) {
if (subscriptions.size === 0) {
await abort;
break;
}
const outcome = await Promise.race([
...[...subscriptions.values()].map((entry) => entry.next),
abort,
]);
if (outcome === "abort")
break;
const subscription = subscriptions.get(outcome.key);
if (!subscription)
continue;
if (outcome.error)
throw outcome.error;
if (outcome.done)
throw new Error(`Dependency stream ${outcome.key} ended unexpectedly`);
subscription.next = subscription.waitNext();
const updated = await evaluate(handler, runtimeContext, ensureSubscription);
const active = new Set(updated.dependencies.map(dependencyKey));
for (const [key, entry] of subscriptions) {
if (!active.has(key)) {
entry.controller.abort();
subscriptions.delete(key);
}
}
if (!equals(ValueSchema, current.value, updated.value)) {
yield create(WatchEventSchema, {
watchId,
value: updated.value,
dependencies: protoDependencies(updated.dependencies),
});
}
current = updated;
signal.removeEventListener("abort", abortAll);
abortAll();
}
}
finally {
context.signal.removeEventListener("abort", abortAll);
abortAll();
execution.finish();
}
},
});
+16
View File
@@ -0,0 +1,16 @@
/** Execution completion, not HTTP disconnection, is the drain boundary. */
export declare const createInvocationRegistry: () => {
begin(id: string): {
signal: AbortSignal;
finish(failed?: boolean): void;
};
status(id: string): {
invocationId: string;
state: string;
};
cancel(id: string): {
invocationId: string;
state: string;
};
};
//# sourceMappingURL=invocations.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"invocations.d.ts","sourceRoot":"","sources":["../src/invocations.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,eAAO,MAAM,wBAAwB;IAGjC,KAAK,KAAK,MAAM;QASZ,MAAM;QACN,MAAM;;IAKV,MAAM,KAAK,MAAM;QACN,YAAY;QAAM,KAAK;;IAElC,MAAM,KAAK,MAAM;;;;CASpB,CAAC"}
+33
View File
@@ -0,0 +1,33 @@
/** Execution completion, not HTTP disconnection, is the drain boundary. */
export const createInvocationRegistry = () => {
const entries = new Map();
return {
begin(id) {
if (!id || entries.has(id))
throw new Error("Invocation ID is missing or already used; invocations are never replayed implicitly");
// Completed identities remain until process retirement. A bounded process
// may reject new work; it must not evict and accidentally replay a call.
if (entries.size >= 100_000)
throw new Error("Invocation registry full; explicit runtime retirement required");
const entry = { state: "running", controller: new AbortController() };
entries.set(id, entry);
return {
signal: entry.controller.signal,
finish(failed = false) {
entry.state = failed ? "failed" : "completed";
},
};
},
status(id) {
return { invocationId: id, state: entries.get(id)?.state ?? "unknown" };
},
cancel(id) {
const entry = entries.get(id);
if (entry && ["running", "cancellation-requested"].includes(entry.state)) {
entry.state = "cancellation-requested";
entry.controller.abort();
}
return this.status(id);
},
};
};
+72
View File
@@ -0,0 +1,72 @@
export type MigrationInput = {
schemaVersion: 1;
executionId: string;
exportId: string;
ports: {
name: string;
binding: string;
view: "old" | "new";
access: ("read" | "write" | "create" | "edge")[];
atomId?: string;
attachedAtomId?: string;
defaultValue?: unknown;
states?: {
objectId: string;
value: unknown;
}[];
edges?: MigrationEdge[];
}[];
};
export type MigrationEdge = {
id: string;
edgeTypeId: string;
firstObjectId: string;
secondObjectId: string;
firstProjectionId: string;
secondProjectionId: string;
firstOrdinal?: number;
secondOrdinal?: number;
firstKeyJson?: string;
secondKeyJson?: string;
};
export type MigrationOutput = {
schemaVersion: 1;
executionId: string;
writes: {
port: string;
objectId: string;
value: unknown;
}[];
creates: {
port: string;
logicalKey: string;
objectId: string;
}[];
edgeReplacements: {
port: string;
edges: MigrationEdge[];
}[];
};
export type MigrationContext = {
enumerate(port: string): {
objectId: string;
value: unknown;
}[];
read(port: string, objectId: string): unknown;
write(port: string, objectId: string, value: unknown): void;
create(port: string, logicalKey: string): string;
edges(port: string): MigrationEdge[];
replaceEdges(port: string, edges: MigrationEdge[]): void;
};
export declare const migrationObjectId: (executionId: string, port: string, logicalKey: string) => string;
/** No ordinary RuntimeContext or network/database clients are supplied here.
* Process isolation belongs to the host, not this convenience API. */
export declare const createMigrationContext: (input: MigrationInput) => {
context: MigrationContext;
result: () => MigrationOutput;
};
/** Entrypoint for an immutable package's dedicated bin/migrate executable.
* stdout is protocol-only; send diagnostics to stderr. The host independently
* validates every write, helper identity, contract, and completion receipt. */
export declare const serveMigration: (exports: Record<string, (context: MigrationContext) => void | Promise<void>>) => Promise<void>;
//# sourceMappingURL=migration.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../src/migration.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,CAAC,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;QACpB,MAAM,EAAE,CAAC,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC;QACjD,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,MAAM,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,OAAO,CAAA;SAAE,EAAE,CAAC;QAChD,KAAK,CAAC,EAAE,aAAa,EAAE,CAAC;KACzB,EAAE,CAAC;CACL,CAAC;AACF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AACF,MAAM,MAAM,eAAe,GAAG;IAC5B,aAAa,EAAE,CAAC,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAC7D,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClE,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,EAAE,CAAA;KAAE,EAAE,CAAC;CAC9D,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAChE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IACjD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAAC;IACrC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;CAC1D,CAAC;AACF,eAAO,MAAM,iBAAiB,gBAAiB,MAAM,QAAQ,MAAM,cAAc,MAAM,WAGnE,CAAC;AAErB;sEACsE;AACtE,eAAO,MAAM,sBAAsB,UAAW,cAAc;;;CAgF3D,CAAC;AAEF;;+EAE+E;AAC/E,eAAO,MAAM,cAAc,YAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,kBAgBhH,CAAC"}
+108
View File
@@ -0,0 +1,108 @@
import { createHash } from "node:crypto";
export const migrationObjectId = (executionId, port, logicalKey) => `obj:migration:${createHash("sha256")
.update(JSON.stringify([executionId, port, logicalKey]))
.digest("hex")}`;
/** No ordinary RuntimeContext or network/database clients are supplied here.
* Process isolation belongs to the host, not this convenience API. */
export const createMigrationContext = (input) => {
if (input.schemaVersion !== 1 ||
!input.executionId ||
new Set(input.ports.map((entry) => entry.name)).size !== input.ports.length)
throw new Error("Invalid migration input");
const output = {
schemaVersion: 1,
executionId: input.executionId,
writes: [],
creates: [],
edgeReplacements: [],
};
const port = (name, access) => {
const selected = input.ports.find((entry) => entry.name === name);
if (!selected?.access.includes(access) || (selected.view === "old" && access !== "read"))
throw new Error(`Migration port ${name} does not grant ${access}`);
return selected;
};
const context = {
enumerate(name) {
const selected = port(name, "read"), states = structuredClone(selected.states ?? []);
if (selected.view === "new" && Object.hasOwn(selected, "defaultValue"))
for (const helper of output.creates) {
if (input.ports.find((entry) => entry.name === helper.port)?.atomId === selected.attachedAtomId &&
!states.some((entry) => entry.objectId === helper.objectId))
states.push({ objectId: helper.objectId, value: structuredClone(selected.defaultValue) });
}
if (selected.view === "new")
for (const write of output.writes) {
if (input.ports.find((entry) => entry.name === write.port)?.binding !== selected.binding)
continue;
const existing = states.findIndex((entry) => entry.objectId === write.objectId), entry = { objectId: write.objectId, value: structuredClone(write.value) };
if (existing < 0)
states.push(entry);
else
states[existing] = entry;
}
return states.sort((a, b) => (a.objectId < b.objectId ? -1 : a.objectId > b.objectId ? 1 : 0));
},
read(name, objectId) {
return context.enumerate(name).find((entry) => entry.objectId === objectId)?.value;
},
write(name, objectId, value) {
port(name, "write");
const previous = output.writes.findIndex((entry) => entry.port === name && entry.objectId === objectId);
const entry = { port: name, objectId, value: structuredClone(value) };
if (previous < 0)
output.writes.push(entry);
else
output.writes[previous] = entry;
},
create(name, logicalKey) {
port(name, "create");
if (!logicalKey || logicalKey.length > 1024)
throw new Error("Migration creation requires a bounded stable logical key");
const objectId = migrationObjectId(input.executionId, name, logicalKey);
if (!output.creates.some((entry) => entry.objectId === objectId))
output.creates.push({ port: name, logicalKey, objectId });
return objectId;
},
edges(name) {
const selected = port(name, "read");
const replacement = selected.view === "new"
? output.edgeReplacements.find((entry) => input.ports.find((candidate) => candidate.name === entry.port)?.binding === selected.binding)
: undefined;
return structuredClone(replacement?.edges ?? selected.edges ?? []);
},
replaceEdges(name, edges) {
port(name, "edge");
const previous = output.edgeReplacements.findIndex((entry) => entry.port === name);
const entry = { port: name, edges: structuredClone(edges) };
if (previous < 0)
output.edgeReplacements.push(entry);
else
output.edgeReplacements[previous] = entry;
},
};
return { context, result: () => structuredClone(output) };
};
/** Entrypoint for an immutable package's dedicated bin/migrate executable.
* stdout is protocol-only; send diagnostics to stderr. The host independently
* validates every write, helper identity, contract, and completion receipt. */
export const serveMigration = async (exports) => {
const chunks = [];
let bytes = 0;
for await (const chunk of process.stdin) {
bytes += chunk.length;
if (bytes > 16 * 1024 * 1024)
throw new Error("Migration input exceeds 16 MiB");
chunks.push(Buffer.from(chunk));
}
const input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
const implementation = Object.hasOwn(exports, input.exportId) ? exports[input.exportId] : undefined;
if (!implementation)
throw new Error("Unknown migration export");
const execution = createMigrationContext(input);
await implementation(execution.context);
const result = JSON.stringify(execution.result());
if (Buffer.byteLength(result) > 16 * 1024 * 1024)
throw new Error("Migration output exceeds 16 MiB");
process.stdout.write(`${result}\n`);
};
+283 -2
View File
@@ -1,5 +1,5 @@
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
import type { CaminoObject, Value } from "../camino/api_pb.js";
import type { CaminoObject, CrdtValue, Value } from "../camino/api_pb.js";
import type { PackageDescriptor } from "./package_pb.js";
import type { CapabilityRef, PackageExportRef } from "./refs_pb.js";
import type { DerivedDependency } from "./runtime_pb.js";
@@ -42,6 +42,46 @@ export type ConstructObjectResponse = Message<"quixos.orch.ConstructObjectRespon
* Use `create(ConstructObjectResponseSchema)` to create a new message.
*/
export declare const ConstructObjectResponseSchema: GenMessage<ConstructObjectResponse>;
/**
* @generated from message quixos.orch.ResolveOrConstructRelatedObjectRequest
*/
export type ResolveOrConstructRelatedObjectRequest = Message<"quixos.orch.ResolveOrConstructRelatedObjectRequest"> & {
/**
* @generated from field: string object_id = 1;
*/
objectId: string;
/**
* @generated from field: string interface_revision_id = 2;
*/
interfaceRevisionId: string;
/**
* @generated from field: string member_id = 3;
*/
memberId: string;
};
/**
* Describes the message quixos.orch.ResolveOrConstructRelatedObjectRequest.
* Use `create(ResolveOrConstructRelatedObjectRequestSchema)` to create a new message.
*/
export declare const ResolveOrConstructRelatedObjectRequestSchema: GenMessage<ResolveOrConstructRelatedObjectRequest>;
/**
* @generated from message quixos.orch.ResolveOrConstructRelatedObjectResponse
*/
export type ResolveOrConstructRelatedObjectResponse = Message<"quixos.orch.ResolveOrConstructRelatedObjectResponse"> & {
/**
* @generated from field: camino.CaminoObject object = 1;
*/
object?: CaminoObject | undefined;
/**
* @generated from field: bool constructed = 2;
*/
constructed: boolean;
};
/**
* Describes the message quixos.orch.ResolveOrConstructRelatedObjectResponse.
* Use `create(ResolveOrConstructRelatedObjectResponseSchema)` to create a new message.
*/
export declare const ResolveOrConstructRelatedObjectResponseSchema: GenMessage<ResolveOrConstructRelatedObjectResponse>;
/**
* @generated from message quixos.orch.InvokeCapabilityRequest
*/
@@ -60,12 +100,43 @@ export type InvokeCapabilityRequest = Message<"quixos.orch.InvokeCapabilityReque
input: {
[key: string]: Value;
};
/**
* Identifies one logical client mutation across the capability and Camino
* layers so a live-value controller can recognize its own confirmation.
*
* @generated from field: string client_mutation_id = 4;
*/
clientMutationId: string;
};
/**
* Describes the message quixos.orch.InvokeCapabilityRequest.
* Use `create(InvokeCapabilityRequestSchema)` to create a new message.
*/
export declare const InvokeCapabilityRequestSchema: GenMessage<InvokeCapabilityRequest>;
/**
* @generated from message quixos.orch.InvokeClassCapabilityRequest
*/
export type InvokeClassCapabilityRequest = Message<"quixos.orch.InvokeClassCapabilityRequest"> & {
/**
* @generated from field: string conformance_id = 1;
*/
conformanceId: string;
/**
* @generated from field: string operation_id = 2;
*/
operationId: string;
/**
* @generated from field: map<string, camino.Value> input = 3;
*/
input: {
[key: string]: Value;
};
};
/**
* Describes the message quixos.orch.InvokeClassCapabilityRequest.
* Use `create(InvokeClassCapabilityRequestSchema)` to create a new message.
*/
export declare const InvokeClassCapabilityRequestSchema: GenMessage<InvokeClassCapabilityRequest>;
/**
* @generated from message quixos.orch.InvokeCapabilityResponse
*/
@@ -90,12 +161,84 @@ export type InvokeCapabilityResponse = Message<"quixos.orch.InvokeCapabilityResp
* @generated from field: string error = 5;
*/
error: string;
/**
* @generated from field: repeated quixos.runtime.DerivedDependency dependencies = 6;
*/
dependencies: DerivedDependency[];
/**
* @generated from field: quixos.orch.FieldEditing field_editing = 7;
*/
fieldEditing?: FieldEditing | undefined;
};
/**
* Describes the message quixos.orch.InvokeCapabilityResponse.
* Use `create(InvokeCapabilityResponseSchema)` to create a new message.
*/
export declare const InvokeCapabilityResponseSchema: GenMessage<InvokeCapabilityResponse>;
/**
* Resolved from the checked native getter/setter binding, not Value.source.
*
* @generated from message quixos.orch.FieldEditing
*/
export type FieldEditing = Message<"quixos.orch.FieldEditing"> & {
/**
* @generated from field: string getter_operation_id = 1;
*/
getterOperationId: string;
/**
* @generated from field: string setter_operation_id = 2;
*/
setterOperationId: string;
/**
* @generated from field: string document_type = 3;
*/
documentType: string;
/**
* @generated from field: string binding_digest = 4;
*/
bindingDigest: string;
};
/**
* Describes the message quixos.orch.FieldEditing.
* Use `create(FieldEditingSchema)` to create a new message.
*/
export declare const FieldEditingSchema: GenMessage<FieldEditing>;
/**
* @generated from message quixos.orch.EditCapabilityFieldRequest
*/
export type EditCapabilityFieldRequest = Message<"quixos.orch.EditCapabilityFieldRequest"> & {
/**
* The public getter; setter must belong to the same value member.
*
* @generated from field: quixos.CapabilityRef capability = 1;
*/
capability?: CapabilityRef | undefined;
/**
* @generated from field: string object_id = 2;
*/
objectId: string;
/**
* @generated from field: string setter_operation_id = 3;
*/
setterOperationId: string;
/**
* @generated from field: string binding_digest = 4;
*/
bindingDigest: string;
/**
* @generated from field: camino.CrdtValue update = 5;
*/
update?: CrdtValue | undefined;
/**
* @generated from field: string client_mutation_id = 6;
*/
clientMutationId: string;
};
/**
* Describes the message quixos.orch.EditCapabilityFieldRequest.
* Use `create(EditCapabilityFieldRequestSchema)` to create a new message.
*/
export declare const EditCapabilityFieldRequestSchema: GenMessage<EditCapabilityFieldRequest>;
/**
* @generated from message quixos.orch.WatchCapabilityRequest
*/
@@ -152,6 +295,10 @@ export type WatchCapabilityEvent = Message<"quixos.orch.WatchCapabilityEvent"> &
* @generated from field: bool initial = 7;
*/
initial: boolean;
/**
* @generated from field: quixos.orch.FieldEditing field_editing = 8;
*/
fieldEditing?: FieldEditing | undefined;
};
/**
* Describes the message quixos.orch.WatchCapabilityEvent.
@@ -161,7 +308,14 @@ export declare const WatchCapabilityEventSchema: GenMessage<WatchCapabilityEvent
/**
* @generated from message quixos.orch.GetWorkspaceRequest
*/
export type GetWorkspaceRequest = Message<"quixos.orch.GetWorkspaceRequest"> & {};
export type GetWorkspaceRequest = Message<"quixos.orch.GetWorkspaceRequest"> & {
/**
* Revision polling must not download the entire interface graph.
*
* @generated from field: bool include_interface_contracts = 1;
*/
includeInterfaceContracts: boolean;
};
/**
* Describes the message quixos.orch.GetWorkspaceRequest.
* Use `create(GetWorkspaceRequestSchema)` to create a new message.
@@ -183,12 +337,115 @@ export type GetWorkspaceResponse = Message<"quixos.orch.GetWorkspaceResponse"> &
* @generated from field: string source_root_commit = 3;
*/
sourceRootCommit: string;
/**
* Checked constructors whose wire input can be empty. The create panel uses
* class factory conformances instead of this constructor inventory.
*
* @generated from field: repeated string empty_input_constructible_atom_ids = 4;
*/
emptyInputConstructibleAtomIds: string[];
/**
* @generated from field: repeated quixos.orch.CapabilityInputContract capability_inputs = 5;
*/
capabilityInputs: CapabilityInputContract[];
/**
* @generated from field: repeated quixos.orch.ConstructorInputContract constructor_inputs = 6;
*/
constructorInputs: ConstructorInputContract[];
/**
* Exact closed interface contracts used by checked presentation consumers.
*
* @generated from field: string interfaces_json = 7;
*/
interfacesJson: string;
/**
* @generated from field: repeated quixos.orch.ClassCapability class_capabilities = 8;
*/
classCapabilities: ClassCapability[];
};
/**
* Describes the message quixos.orch.GetWorkspaceResponse.
* Use `create(GetWorkspaceResponseSchema)` to create a new message.
*/
export declare const GetWorkspaceResponseSchema: GenMessage<GetWorkspaceResponse>;
/**
* @generated from message quixos.orch.ClassCapability
*/
export type ClassCapability = Message<"quixos.orch.ClassCapability"> & {
/**
* @generated from field: string conformance_id = 1;
*/
conformanceId: string;
/**
* @generated from field: string atom_id = 2;
*/
atomId: string;
/**
* @generated from field: string interface_revision_id = 3;
*/
interfaceRevisionId: string;
/**
* @generated from field: string definition_id = 4;
*/
definitionId: string;
/**
* @generated from field: string operation_id = 5;
*/
operationId: string;
/**
* @generated from field: string input_type_json = 6;
*/
inputTypeJson: string;
/**
* @generated from field: string output_type_json = 7;
*/
outputTypeJson: string;
};
/**
* Describes the message quixos.orch.ClassCapability.
* Use `create(ClassCapabilitySchema)` to create a new message.
*/
export declare const ClassCapabilitySchema: GenMessage<ClassCapability>;
/**
* @generated from message quixos.orch.CapabilityInputContract
*/
export type CapabilityInputContract = Message<"quixos.orch.CapabilityInputContract"> & {
/**
* @generated from field: string interface_revision_id = 1;
*/
interfaceRevisionId: string;
/**
* @generated from field: string operation_id = 2;
*/
operationId: string;
/**
* @generated from field: string type_json = 3;
*/
typeJson: string;
};
/**
* Describes the message quixos.orch.CapabilityInputContract.
* Use `create(CapabilityInputContractSchema)` to create a new message.
*/
export declare const CapabilityInputContractSchema: GenMessage<CapabilityInputContract>;
/**
* @generated from message quixos.orch.ConstructorInputContract
*/
export type ConstructorInputContract = Message<"quixos.orch.ConstructorInputContract"> & {
/**
* @generated from field: string atom_id = 1;
*/
atomId: string;
/**
* @generated from field: string type_json = 2;
*/
typeJson: string;
};
/**
* Describes the message quixos.orch.ConstructorInputContract.
* Use `create(ConstructorInputContractSchema)` to create a new message.
*/
export declare const ConstructorInputContractSchema: GenMessage<ConstructorInputContract>;
/**
* @generated from message quixos.orch.ListActivationsRequest
*/
@@ -410,6 +667,22 @@ export declare const OrchestratorRuntime: GenService<{
input: typeof InvokeCapabilityRequestSchema;
output: typeof InvokeCapabilityResponseSchema;
};
/**
* @generated from rpc quixos.orch.OrchestratorRuntime.EditCapabilityField
*/
editCapabilityField: {
methodKind: "unary";
input: typeof EditCapabilityFieldRequestSchema;
output: typeof InvokeCapabilityResponseSchema;
};
/**
* @generated from rpc quixos.orch.OrchestratorRuntime.InvokeClassCapability
*/
invokeClassCapability: {
methodKind: "unary";
input: typeof InvokeClassCapabilityRequestSchema;
output: typeof InvokeCapabilityResponseSchema;
};
/**
* @generated from rpc quixos.orch.OrchestratorRuntime.WatchCapability
*/
@@ -426,6 +699,14 @@ export declare const OrchestratorRuntime: GenService<{
input: typeof ConstructObjectRequestSchema;
output: typeof ConstructObjectResponseSchema;
};
/**
* @generated from rpc quixos.orch.OrchestratorRuntime.ResolveOrConstructRelatedObject
*/
resolveOrConstructRelatedObject: {
methodKind: "unary";
input: typeof ResolveOrConstructRelatedObjectRequestSchema;
output: typeof ResolveOrConstructRelatedObjectResponseSchema;
};
/**
* @generated from rpc quixos.orch.OrchestratorRuntime.GetWorkspace
*/
+1 -1
View File
File diff suppressed because one or more lines are too long
+57 -17
View File
File diff suppressed because one or more lines are too long
+9 -2
View File
@@ -65,10 +65,10 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
case: "edge";
} | {
/**
* @generated from field: string receiver_interface_revision_id = 4;
* @generated from field: string interface_revision_id = 4;
*/
value: string;
case: "receiverInterfaceRevisionId";
case: "interfaceRevisionId";
} | {
/**
* @generated from field: string constructor_atom_id = 5;
@@ -79,6 +79,13 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
case: undefined;
value?: undefined;
};
/**
* Defaults to the invocation receiver. A checked dependency traversal can
* select a related object explicitly before the package runtime starts.
*
* @generated from field: string object_id = 6;
*/
objectId: string;
};
/**
* Describes the message quixos.InjectedDependency.
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"refs_pb.d.ts","sourceRoot":"","sources":["../../src/quixos/refs_pb.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAExE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,OACsiB,CAAC;AAEtkB;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,sBAAsB,CAAC,GAAG;IAC5D;;OAEG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,UAAU,CAAC,aAAa,CACxB,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,yBAAyB,CAAC,GAAG;IAClE;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,UAAU,CAAC,gBAAgB,CAC9B,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,2BAA2B,CAAC,GAAG;IACtE;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE;QACP;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,aAAa,CAAC;KACrB,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,cAAc,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;KACd,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,6BAA6B,CAAC;KACrC,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,mBAAmB,CAAC;KAC3B,GAAG;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC;CAC5C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,UAAU,CAAC,kBAAkB,CAClC,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,uBAAuB,CAAC,GAAG;IAC9D;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,UAAU,CAAC,cAAc,CAC1B,CAAC"}
{"version":3,"file":"refs_pb.d.ts","sourceRoot":"","sources":["../../src/quixos/refs_pb.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAExE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,OACmjB,CAAC;AAEnlB;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,sBAAsB,CAAC,GAAG;IAC5D;;OAEG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,UAAU,CAAC,aAAa,CACxB,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,yBAAyB,CAAC,GAAG;IAClE;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,UAAU,CAAC,gBAAgB,CAC9B,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,2BAA2B,CAAC,GAAG;IACtE;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE;QACP;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,aAAa,CAAC;KACrB,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,cAAc,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;KACd,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,qBAAqB,CAAC;KAC7B,GAAG;QACF;;WAEG;QACH,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,mBAAmB,CAAC;KAC3B,GAAG;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,SAAS,CAAA;KAAE,CAAC;IAE3C;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,UAAU,CAAC,kBAAkB,CAClC,CAAC;AAEnC;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,uBAAuB,CAAC,GAAG;IAC9D;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,UAAU,CAAC,cAAc,CAC1B,CAAC"}
+1 -1
View File
@@ -5,7 +5,7 @@ import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
/**
* Describes the file quixos/refs.proto.
*/
export const file_quixos_refs = /*@__PURE__*/ fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zIkQKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJIroBChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEigKHnJlY2VpdmVyX2ludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIAEIJCgdiaW5kaW5nIj0KDkVkZ2VEZXBlbmRlbmN5EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIVCg1wcm9qZWN0aW9uX2lkGAIgASgJYgZwcm90bzM");
export const file_quixos_refs = /*@__PURE__*/ fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zIkQKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJIsQBChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEh8KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIABIRCglvYmplY3RfaWQYBiABKAlCCQoHYmluZGluZyI9Cg5FZGdlRGVwZW5kZW5jeRIUCgxlZGdlX3R5cGVfaWQYASABKAkSFQoNcHJvamVjdGlvbl9pZBgCIAEoCWIGcHJvdG8z");
/**
* Describes the message quixos.CapabilityRef.
* Use `create(CapabilityRefSchema)` to create a new message.
+114
View File
@@ -14,6 +14,10 @@ export type HandshakeRequest = Message<"quixos.runtime.HandshakeRequest"> & {
* @generated from field: string orch_protocol_version = 1;
*/
orchProtocolVersion: string;
/**
* @generated from field: string nonce = 2;
*/
nonce: string;
};
/**
* Describes the message quixos.runtime.HandshakeRequest.
@@ -36,12 +40,94 @@ export type HandshakeResponse = Message<"quixos.runtime.HandshakeResponse"> & {
* @generated from field: repeated string export_ids = 3;
*/
exportIds: string[];
/**
* @generated from field: string instance_id = 4;
*/
instanceId: string;
/**
* @generated from field: string authentication_proof = 5;
*/
authenticationProof: string;
/**
* @generated from field: repeated string capabilities = 6;
*/
capabilities: string[];
};
/**
* Describes the message quixos.runtime.HandshakeResponse.
* Use `create(HandshakeResponseSchema)` to create a new message.
*/
export declare const HandshakeResponseSchema: GenMessage<HandshakeResponse>;
/**
* @generated from message quixos.runtime.InvocationContext
*/
export type InvocationContext = Message<"quixos.runtime.InvocationContext"> & {
/**
* @generated from field: string workspace_epoch = 1;
*/
workspaceEpoch: string;
/**
* @generated from field: string instance_id = 2;
*/
instanceId: string;
/**
* @generated from field: string binding_digest = 3;
*/
bindingDigest: string;
/**
* @generated from field: string grant = 4;
*/
grant: string;
/**
* @generated from field: string session_id = 5;
*/
sessionId: string;
/**
* Host-selected owner; packages must not invent workspace-local ownership.
*
* @generated from field: string owner_conformance_id = 6;
*/
ownerConformanceId: string;
};
/**
* Describes the message quixos.runtime.InvocationContext.
* Use `create(InvocationContextSchema)` to create a new message.
*/
export declare const InvocationContextSchema: GenMessage<InvocationContext>;
/**
* @generated from message quixos.runtime.InvocationControlRequest
*/
export type InvocationControlRequest = Message<"quixos.runtime.InvocationControlRequest"> & {
/**
* @generated from field: string invocation_id = 1;
*/
invocationId: string;
};
/**
* Describes the message quixos.runtime.InvocationControlRequest.
* Use `create(InvocationControlRequestSchema)` to create a new message.
*/
export declare const InvocationControlRequestSchema: GenMessage<InvocationControlRequest>;
/**
* @generated from message quixos.runtime.InvocationStatus
*/
export type InvocationStatus = Message<"quixos.runtime.InvocationStatus"> & {
/**
* @generated from field: string invocation_id = 1;
*/
invocationId: string;
/**
* unknown, running, cancellation-requested, completed, failed
*
* @generated from field: string state = 2;
*/
state: string;
};
/**
* Describes the message quixos.runtime.InvocationStatus.
* Use `create(InvocationStatusSchema)` to create a new message.
*/
export declare const InvocationStatusSchema: GenMessage<InvocationStatus>;
/**
* @generated from message quixos.runtime.InvokeRequest
*/
@@ -68,6 +154,10 @@ export type InvokeRequest = Message<"quixos.runtime.InvokeRequest"> & {
* @generated from field: repeated quixos.InjectedDependency dependencies = 5;
*/
dependencies: InjectedDependency[];
/**
* @generated from field: quixos.runtime.InvocationContext context = 6;
*/
context?: InvocationContext | undefined;
};
/**
* Describes the message quixos.runtime.InvokeRequest.
@@ -90,6 +180,10 @@ export type InvokeResponse = Message<"quixos.runtime.InvokeResponse"> & {
* @generated from field: string error = 3;
*/
error: string;
/**
* @generated from field: repeated quixos.runtime.DerivedDependency dependencies = 4;
*/
dependencies: DerivedDependency[];
};
/**
* Describes the message quixos.runtime.InvokeResponse.
@@ -122,6 +216,10 @@ export type WatchRequest = Message<"quixos.runtime.WatchRequest"> & {
* @generated from field: repeated quixos.InjectedDependency dependencies = 5;
*/
dependencies: InjectedDependency[];
/**
* @generated from field: quixos.runtime.InvocationContext context = 6;
*/
context?: InvocationContext | undefined;
};
/**
* Describes the message quixos.runtime.WatchRequest.
@@ -212,5 +310,21 @@ export declare const PackageRuntime: GenService<{
input: typeof WatchRequestSchema;
output: typeof WatchEventSchema;
};
/**
* @generated from rpc quixos.runtime.PackageRuntime.GetInvocationStatus
*/
getInvocationStatus: {
methodKind: "unary";
input: typeof InvocationControlRequestSchema;
output: typeof InvocationStatusSchema;
};
/**
* @generated from rpc quixos.runtime.PackageRuntime.CancelInvocation
*/
cancelInvocation: {
methodKind: "unary";
input: typeof InvocationControlRequestSchema;
output: typeof InvocationStatusSchema;
};
}>;
//# sourceMappingURL=runtime_pb.d.ts.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"runtime_pb.d.ts","sourceRoot":"","sources":["../../src/quixos/runtime_pb.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"runtime_pb.d.ts","sourceRoot":"","sources":["../../src/quixos/runtime_pb.ts"],"names":[],"mappings":"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"}
+21 -6
View File
@@ -7,7 +7,7 @@ import { file_quixos_refs } from "./refs_pb.js";
/**
* Describes the file quixos/runtime.proto.
*/
export const file_quixos_runtime = /*@__PURE__*/ fileDesc("ChRxdWl4b3MvcnVudGltZS5wcm90bxIOcXVpeG9zLnJ1bnRpbWUiMQoQSGFuZHNoYWtlUmVxdWVzdBIdChVvcmNoX3Byb3RvY29sX3ZlcnNpb24YASABKAkiZgoRSGFuZHNoYWtlUmVzcG9uc2USGwoTcGFja2FnZV9yZXZpc2lvbl9pZBgBIAEoCRIgChhydW50aW1lX3Byb3RvY29sX3ZlcnNpb24YAiABKAkSEgoKZXhwb3J0X2lkcxgDIAMoCSKLAgoNSW52b2tlUmVxdWVzdBIVCg1pbnZvY2F0aW9uX2lkGAEgASgJEigKBmV4cG9ydBgCIAEoCzIYLnF1aXhvcy5QYWNrYWdlRXhwb3J0UmVmEhEKCW9iamVjdF9pZBgDIAEoCRI3CgVpbnB1dBgEIAMoCzIoLnF1aXhvcy5ydW50aW1lLkludm9rZVJlcXVlc3QuSW5wdXRFbnRyeRIwCgxkZXBlbmRlbmNpZXMYBSADKAsyGi5xdWl4b3MuSW5qZWN0ZWREZXBlbmRlbmN5GjsKCklucHV0RW50cnkSCwoDa2V5GAEgASgJEhwKBXZhbHVlGAIgASgLMg0uY2FtaW5vLlZhbHVlOgI4ASJKCg5JbnZva2VSZXNwb25zZRIKCgJvaxgBIAEoCBIdCgZyZXN1bHQYAiABKAsyDS5jYW1pbm8uVmFsdWUSDQoFZXJyb3IYAyABKAkiiQIKDFdhdGNoUmVxdWVzdBIVCg1pbnZvY2F0aW9uX2lkGAEgASgJEigKBmV4cG9ydBgCIAEoCzIYLnF1aXhvcy5QYWNrYWdlRXhwb3J0UmVmEhEKCW9iamVjdF9pZBgDIAEoCRI2CgVpbnB1dBgEIAMoCzInLnF1aXhvcy5ydW50aW1lLldhdGNoUmVxdWVzdC5JbnB1dEVudHJ5EjAKDGRlcGVuZGVuY2llcxgFIAMoCzIaLnF1aXhvcy5JbmplY3RlZERlcGVuZGVuY3kaOwoKSW5wdXRFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBImIKEURlcml2ZWREZXBlbmRlbmN5EgwKBGtpbmQYASABKAkSEQoJb2JqZWN0X2lkGAIgASgJEhUKDWF0dGFjaG1lbnRfaWQYAyABKAkSFQoNcHJvamVjdGlvbl9pZBgEIAEoCSKVAQoKV2F0Y2hFdmVudBIQCgh3YXRjaF9pZBgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZRI3CgxkZXBlbmRlbmNpZXMYAyADKAsyIS5xdWl4b3MucnVudGltZS5EZXJpdmVkRGVwZW5kZW5jeRINCgVlcnJvchgEIAEoCRIPCgdpbml0aWFsGAUgASgIMvABCg5QYWNrYWdlUnVudGltZRJQCglIYW5kc2hha2USIC5xdWl4b3MucnVudGltZS5IYW5kc2hha2VSZXF1ZXN0GiEucXVpeG9zLnJ1bnRpbWUuSGFuZHNoYWtlUmVzcG9uc2USRwoGSW52b2tlEh0ucXVpeG9zLnJ1bnRpbWUuSW52b2tlUmVxdWVzdBoeLnF1aXhvcy5ydW50aW1lLkludm9rZVJlc3BvbnNlEkMKBVdhdGNoEhwucXVpeG9zLnJ1bnRpbWUuV2F0Y2hSZXF1ZXN0GhoucXVpeG9zLnJ1bnRpbWUuV2F0Y2hFdmVudDABYgZwcm90bzM", [file_camino_api, file_quixos_refs]);
export const file_quixos_runtime = /*@__PURE__*/ fileDesc("ChRxdWl4b3MvcnVudGltZS5wcm90bxIOcXVpeG9zLnJ1bnRpbWUiQAoQSGFuZHNoYWtlUmVxdWVzdBIdChVvcmNoX3Byb3RvY29sX3ZlcnNpb24YASABKAkSDQoFbm9uY2UYAiABKAkirwEKEUhhbmRzaGFrZVJlc3BvbnNlEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSIAoYcnVudGltZV9wcm90b2NvbF92ZXJzaW9uGAIgASgJEhIKCmV4cG9ydF9pZHMYAyADKAkSEwoLaW5zdGFuY2VfaWQYBCABKAkSHAoUYXV0aGVudGljYXRpb25fcHJvb2YYBSABKAkSFAoMY2FwYWJpbGl0aWVzGAYgAygJIpoBChFJbnZvY2F0aW9uQ29udGV4dBIXCg93b3Jrc3BhY2VfZXBvY2gYASABKAkSEwoLaW5zdGFuY2VfaWQYAiABKAkSFgoOYmluZGluZ19kaWdlc3QYAyABKAkSDQoFZ3JhbnQYBCABKAkSEgoKc2Vzc2lvbl9pZBgFIAEoCRIcChRvd25lcl9jb25mb3JtYW5jZV9pZBgGIAEoCSIxChhJbnZvY2F0aW9uQ29udHJvbFJlcXVlc3QSFQoNaW52b2NhdGlvbl9pZBgBIAEoCSI4ChBJbnZvY2F0aW9uU3RhdHVzEhUKDWludm9jYXRpb25faWQYASABKAkSDQoFc3RhdGUYAiABKAkivwIKDUludm9rZVJlcXVlc3QSFQoNaW52b2NhdGlvbl9pZBgBIAEoCRIoCgZleHBvcnQYAiABKAsyGC5xdWl4b3MuUGFja2FnZUV4cG9ydFJlZhIRCglvYmplY3RfaWQYAyABKAkSNwoFaW5wdXQYBCADKAsyKC5xdWl4b3MucnVudGltZS5JbnZva2VSZXF1ZXN0LklucHV0RW50cnkSMAoMZGVwZW5kZW5jaWVzGAUgAygLMhoucXVpeG9zLkluamVjdGVkRGVwZW5kZW5jeRIyCgdjb250ZXh0GAYgASgLMiEucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvbkNvbnRleHQaOwoKSW5wdXRFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBIoMBCg5JbnZva2VSZXNwb25zZRIKCgJvaxgBIAEoCBIdCgZyZXN1bHQYAiABKAsyDS5jYW1pbm8uVmFsdWUSDQoFZXJyb3IYAyABKAkSNwoMZGVwZW5kZW5jaWVzGAQgAygLMiEucXVpeG9zLnJ1bnRpbWUuRGVyaXZlZERlcGVuZGVuY3kivQIKDFdhdGNoUmVxdWVzdBIVCg1pbnZvY2F0aW9uX2lkGAEgASgJEigKBmV4cG9ydBgCIAEoCzIYLnF1aXhvcy5QYWNrYWdlRXhwb3J0UmVmEhEKCW9iamVjdF9pZBgDIAEoCRI2CgVpbnB1dBgEIAMoCzInLnF1aXhvcy5ydW50aW1lLldhdGNoUmVxdWVzdC5JbnB1dEVudHJ5EjAKDGRlcGVuZGVuY2llcxgFIAMoCzIaLnF1aXhvcy5JbmplY3RlZERlcGVuZGVuY3kSMgoHY29udGV4dBgGIAEoCzIhLnF1aXhvcy5ydW50aW1lLkludm9jYXRpb25Db250ZXh0GjsKCklucHV0RW50cnkSCwoDa2V5GAEgASgJEhwKBXZhbHVlGAIgASgLMg0uY2FtaW5vLlZhbHVlOgI4ASJiChFEZXJpdmVkRGVwZW5kZW5jeRIMCgRraW5kGAEgASgJEhEKCW9iamVjdF9pZBgCIAEoCRIVCg1hdHRhY2htZW50X2lkGAMgASgJEhUKDXByb2plY3Rpb25faWQYBCABKAkilQEKCldhdGNoRXZlbnQSEAoId2F0Y2hfaWQYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWUSNwoMZGVwZW5kZW5jaWVzGAMgAygLMiEucXVpeG9zLnJ1bnRpbWUuRGVyaXZlZERlcGVuZGVuY3kSDQoFZXJyb3IYBCABKAkSDwoHaW5pdGlhbBgFIAEoCDKzAwoOUGFja2FnZVJ1bnRpbWUSUAoJSGFuZHNoYWtlEiAucXVpeG9zLnJ1bnRpbWUuSGFuZHNoYWtlUmVxdWVzdBohLnF1aXhvcy5ydW50aW1lLkhhbmRzaGFrZVJlc3BvbnNlEkcKBkludm9rZRIdLnF1aXhvcy5ydW50aW1lLkludm9rZVJlcXVlc3QaHi5xdWl4b3MucnVudGltZS5JbnZva2VSZXNwb25zZRJDCgVXYXRjaBIcLnF1aXhvcy5ydW50aW1lLldhdGNoUmVxdWVzdBoaLnF1aXhvcy5ydW50aW1lLldhdGNoRXZlbnQwARJhChNHZXRJbnZvY2F0aW9uU3RhdHVzEigucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvbkNvbnRyb2xSZXF1ZXN0GiAucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvblN0YXR1cxJeChBDYW5jZWxJbnZvY2F0aW9uEigucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvbkNvbnRyb2xSZXF1ZXN0GiAucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvblN0YXR1c2IGcHJvdG8z", [file_camino_api, file_quixos_refs]);
/**
* Describes the message quixos.runtime.HandshakeRequest.
* Use `create(HandshakeRequestSchema)` to create a new message.
@@ -18,31 +18,46 @@ export const HandshakeRequestSchema = /*@__PURE__*/ messageDesc(file_quixos_runt
* Use `create(HandshakeResponseSchema)` to create a new message.
*/
export const HandshakeResponseSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 1);
/**
* Describes the message quixos.runtime.InvocationContext.
* Use `create(InvocationContextSchema)` to create a new message.
*/
export const InvocationContextSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 2);
/**
* Describes the message quixos.runtime.InvocationControlRequest.
* Use `create(InvocationControlRequestSchema)` to create a new message.
*/
export const InvocationControlRequestSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 3);
/**
* Describes the message quixos.runtime.InvocationStatus.
* Use `create(InvocationStatusSchema)` to create a new message.
*/
export const InvocationStatusSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 4);
/**
* Describes the message quixos.runtime.InvokeRequest.
* Use `create(InvokeRequestSchema)` to create a new message.
*/
export const InvokeRequestSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 2);
export const InvokeRequestSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 5);
/**
* Describes the message quixos.runtime.InvokeResponse.
* Use `create(InvokeResponseSchema)` to create a new message.
*/
export const InvokeResponseSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 3);
export const InvokeResponseSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 6);
/**
* Describes the message quixos.runtime.WatchRequest.
* Use `create(WatchRequestSchema)` to create a new message.
*/
export const WatchRequestSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 4);
export const WatchRequestSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 7);
/**
* Describes the message quixos.runtime.DerivedDependency.
* Use `create(DerivedDependencySchema)` to create a new message.
*/
export const DerivedDependencySchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 5);
export const DerivedDependencySchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 8);
/**
* Describes the message quixos.runtime.WatchEvent.
* Use `create(WatchEventSchema)` to create a new message.
*/
export const WatchEventSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 6);
export const WatchEventSchema = /*@__PURE__*/ messageDesc(file_quixos_runtime, 9);
/**
* @generated from service quixos.runtime.PackageRuntime
*/
+14
View File
@@ -0,0 +1,14 @@
declare const referenceBrand: unique symbol;
export interface QxObjectRef<Identity extends string = string> {
readonly [referenceBrand]: {
readonly [K in Identity]: true;
};
equals(other: QxObjectRef<string>): boolean;
}
export declare const isObjectReference: (value: unknown) => value is QxObjectRef;
/** Internal transport boundary; intentionally not exported from the SDK entry. */
export declare const referenceFromWire: (id: string) => QxObjectRef;
export declare const referenceToWire: (value: unknown) => string;
export declare const assertReferenceFree: (value: unknown, seen?: Set<object>) => void;
export {};
//# sourceMappingURL=references.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"references.d.ts","sourceRoot":"","sources":["../src/references.ts"],"names":[],"mappings":"AAGA,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAC5C,MAAM,WAAW,WAAW,CAAC,QAAQ,SAAS,MAAM,GAAG,MAAM;IAC3D,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,QAAQ,GAAG,IAAI;KAAE,CAAC;IAC9D,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;CAC7C;AAmBD,eAAO,MAAM,iBAAiB,UAAW,OAAO,KAAG,KAAK,IAAI,WACU,CAAC;AAEvE,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,OAAQ,MAAM,KAAG,WAG9C,CAAC;AACF,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,MAGhD,CAAC;AACF,eAAO,MAAM,mBAAmB,UAAW,OAAO,yBAA6B,IAU9E,CAAC"}
+46
View File
@@ -0,0 +1,46 @@
/** Opaque runtime identity. The wire codec, never ordinary package state, owns
* the raw ID. These handles do not themselves confer authority or a lease. */
const identities = new WeakMap();
class Reference {
constructor(id) {
identities.set(this, id);
Object.freeze(this);
}
equals(other) {
return isObjectReference(other) && identities.get(this) === identities.get(other);
}
toJSON() {
throw new Error("Object references cannot be serialized into ordinary data");
}
toString() {
throw new Error("Object references cannot be coerced to strings");
}
[Symbol.toPrimitive]() {
throw new Error("Object references cannot be coerced to scalar values");
}
}
export const isObjectReference = (value) => typeof value === "object" && value !== null && identities.has(value);
/** Internal transport boundary; intentionally not exported from the SDK entry. */
export const referenceFromWire = (id) => {
if (typeof id !== "string" || !id)
throw new Error("Missing object reference identity");
return new Reference(id);
};
export const referenceToWire = (value) => {
if (!isObjectReference(value))
throw new Error("Expected an opaque object reference, not a raw ID");
return identities.get(value);
};
export const assertReferenceFree = (value, seen = new Set()) => {
if (!value || typeof value !== "object")
return;
if (isObjectReference(value))
throw new Error("Managed references belong in declared RPC references or graph relationships, not ordinary state/messages");
if (seen.has(value))
throw new Error("Cyclic ordinary data");
seen.add(value);
if (!(value instanceof Uint8Array))
for (const child of Object.values(value))
assertReferenceFree(child, seen);
seen.delete(value);
};
+30
View File
@@ -0,0 +1,30 @@
import type { QxObjectRef } from "./references.js";
import type { RelationshipCollection, RelationshipEntry } from "./index.js";
type Key = string | boolean | bigint;
type Port<T extends QxObjectRef> = {
collection(): Promise<RelationshipCollection<T>>;
replace(entries: RelationshipEntry<T>[], expectedRevision: bigint): Promise<RelationshipCollection<T>>;
};
/** Helpers never retry a failed CAS or silently overwrite concurrent edits. */
export declare const relationshipMap: <T extends QxObjectRef, K extends Key = Key>(port: Port<T>) => {
read: () => Promise<RelationshipCollection<T>>;
get(key: K): Promise<{
revision: bigint;
value: T | undefined;
}>;
set(key: K, target: T, expectedRevision: bigint): Promise<RelationshipCollection<T>>;
delete(key: K, expectedRevision: bigint): Promise<RelationshipCollection<T>>;
};
export declare const relationshipList: <T extends QxObjectRef>(port: Port<T>) => {
read: () => Promise<RelationshipCollection<T>>;
insert(index: number, target: T, expectedRevision: bigint): Promise<RelationshipCollection<T>>;
move(edgeId: string, index: number, expectedRevision: bigint): Promise<RelationshipCollection<T>>;
delete(edgeId: string, expectedRevision: bigint): Promise<RelationshipCollection<T>>;
};
export declare const relationshipSet: <T extends QxObjectRef>(port: Port<T>) => {
read: () => Promise<RelationshipCollection<T>>;
add(target: T, expectedRevision: bigint): Promise<RelationshipCollection<T>>;
delete(target: T, expectedRevision: bigint): Promise<RelationshipCollection<T>>;
};
export {};
//# sourceMappingURL=relationships.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../src/relationships.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC5E,KAAK,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AACrC,KAAK,IAAI,CAAC,CAAC,SAAS,WAAW,IAAI;IACjC,UAAU,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC;CACxG,CAAC;AAMF,+EAA+E;AAC/E,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,GAAG,GAAG,GAAG,QAAQ,IAAI,CAAC,CAAC,CAAC;IACvF,IAAI;IACE,GAAG,MAAM,CAAC;;;;IAIV,GAAG,MAAM,CAAC,UAAU,CAAC,oBAAoB,MAAM;IAO/C,MAAM,MAAM,CAAC,oBAAoB,MAAM;CAO7C,CAAC;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,WAAW,QAAQ,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI;IACE,MAAM,QAAQ,MAAM,UAAU,CAAC,oBAAoB,MAAM;IAOzD,IAAI,SAAS,MAAM,SAAS,MAAM,oBAAoB,MAAM;IAS5D,MAAM,SAAS,MAAM,oBAAoB,MAAM;CAQrD,CAAC;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,WAAW,QAAQ,IAAI,CAAC,CAAC,CAAC;IAClE,IAAI;IACE,GAAG,SAAS,CAAC,oBAAoB,MAAM;IAKvC,MAAM,SAAS,CAAC,oBAAoB,MAAM;CAOhD,CAAC"}
+63
View File
@@ -0,0 +1,63 @@
const checked = async (port, revision) => {
const snapshot = await port.collection();
if (snapshot.revision !== revision)
throw new Error("STALE_COLLECTION_REVISION");
return snapshot;
};
/** Helpers never retry a failed CAS or silently overwrite concurrent edits. */
export const relationshipMap = (port) => ({
read: () => port.collection(),
async get(key) {
const snapshot = await port.collection();
return { revision: snapshot.revision, value: snapshot.entries.find((entry) => entry.key === key)?.target };
},
async set(key, target, expectedRevision) {
const snapshot = await checked(port, expectedRevision);
const entries = snapshot.entries.filter((entry) => entry.key !== key);
const existing = snapshot.entries.find((entry) => entry.key === key && entry.target.equals(target));
entries.push(existing ?? { key, target });
return port.replace(entries, expectedRevision);
},
async delete(key, expectedRevision) {
const snapshot = await checked(port, expectedRevision);
return port.replace(snapshot.entries.filter((entry) => entry.key !== key), expectedRevision);
},
});
export const relationshipList = (port) => ({
read: () => port.collection(),
async insert(index, target, expectedRevision) {
const snapshot = await checked(port, expectedRevision);
if (!Number.isSafeInteger(index) || index < 0 || index > snapshot.entries.length)
throw new Error("List index out of bounds");
snapshot.entries.splice(index, 0, { target });
return port.replace(snapshot.entries, expectedRevision);
},
async move(edgeId, index, expectedRevision) {
const snapshot = await checked(port, expectedRevision);
const prior = snapshot.entries.findIndex((entry) => entry.edgeId === edgeId);
if (prior < 0 || !Number.isSafeInteger(index) || index < 0 || index >= snapshot.entries.length)
throw new Error("Unknown list entry or invalid index");
const [entry] = snapshot.entries.splice(prior, 1);
snapshot.entries.splice(index, 0, entry);
return port.replace(snapshot.entries, expectedRevision);
},
async delete(edgeId, expectedRevision) {
const snapshot = await checked(port, expectedRevision);
if (!snapshot.entries.some((entry) => entry.edgeId === edgeId))
throw new Error("Unknown list entry");
return port.replace(snapshot.entries.filter((entry) => entry.edgeId !== edgeId), expectedRevision);
},
});
export const relationshipSet = (port) => ({
read: () => port.collection(),
async add(target, expectedRevision) {
const snapshot = await checked(port, expectedRevision);
if (snapshot.entries.some((entry) => entry.target.equals(target)))
return snapshot;
return port.replace([...snapshot.entries, { target }], expectedRevision);
},
async delete(target, expectedRevision) {
const snapshot = await checked(port, expectedRevision);
return port.replace(snapshot.entries.filter((entry) => !entry.target.equals(target)), expectedRevision);
},
});
Generated
+14 -14
View File
@@ -74,17 +74,17 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1788542526,
"narHash": "sha256-NaEdW9HKjRpOhZ8TBs6ieD/YuJ2jDyLPVk076wk6Kzs=",
"ref": "refs/tags/quixos-reachability/27373d801b31fbced51cc33bc2a9c92499a46ba3",
"rev": "27373d801b31fbced51cc33bc2a9c92499a46ba3",
"revCount": 28,
"lastModified": 1789602549,
"narHash": "sha256-/riIaX4zEud3wPtGqI5pGm+SbHCrsqVTLZOacfOdYM4=",
"ref": "refs/tags/quixos-reachability/499b65b274f07d151c61e7710929a6323b6549a7",
"rev": "499b65b274f07d151c61e7710929a6323b6549a7",
"revCount": 74,
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
},
"original": {
"ref": "refs/tags/quixos-reachability/27373d801b31fbced51cc33bc2a9c92499a46ba3",
"rev": "27373d801b31fbced51cc33bc2a9c92499a46ba3",
"ref": "refs/tags/quixos-reachability/499b65b274f07d151c61e7710929a6323b6549a7",
"rev": "499b65b274f07d151c61e7710929a6323b6549a7",
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
}
@@ -92,17 +92,17 @@
"quixosNixHelpers": {
"flake": false,
"locked": {
"lastModified": 1788543053,
"narHash": "sha256-3YReATWEVxZinYVi/l3cwOz+oFoZUrwj24kUBQTmUpQ=",
"ref": "refs/tags/quixos-reachability/56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a",
"rev": "56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a",
"revCount": 8,
"lastModified": 1788574090,
"narHash": "sha256-bSgc0LS2Ub01fnE14VqazdZzFuVPl4sykmA2d1l4kDs=",
"ref": "refs/tags/quixos-reachability/7177130c0365f2fa58ea4877366e1c5d17db4c01",
"rev": "7177130c0365f2fa58ea4877366e1c5d17db4c01",
"revCount": 4,
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
},
"original": {
"ref": "refs/tags/quixos-reachability/56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a",
"rev": "56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a",
"ref": "refs/tags/quixos-reachability/7177130c0365f2fa58ea4877366e1c5d17db4c01",
"rev": "7177130c0365f2fa58ea4877366e1c5d17db4c01",
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
}
+60 -21
View File
@@ -4,33 +4,72 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
quixos-protocol.url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git?ref=refs/tags/quixos-reachability/27373d801b31fbced51cc33bc2a9c92499a46ba3&rev=27373d801b31fbced51cc33bc2a9c92499a46ba3";
quixos-protocol.url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git?ref=refs/tags/quixos-reachability/499b65b274f07d151c61e7710929a6323b6549a7&rev=499b65b274f07d151c61e7710929a6323b6549a7";
quixosNixHelpers = {
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/tags/quixos-reachability/56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a&rev=56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a";
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/tags/quixos-reachability/7177130c0365f2fa58ea4877366e1c5d17db4c01&rev=7177130c0365f2fa58ea4877366e1c5d17db4c01";
flake = false;
};
};
outputs = inputs@{ nixpkgs, flake-utils, quixosNixHelpers, ... }:
outputs =
inputs@{
nixpkgs,
flake-utils,
quixosNixHelpers,
...
}:
let
quixosHelpers = import "${quixosNixHelpers}/quixos-package-helpers.nix";
in
quixosHelpers.mkCaminoTsYarnNixifyFlake {
inherit inputs nixpkgs flake-utils;
packageRoot = ./.;
sourceName = "quixos-camino-package-runtime-source";
promptName = "camino-package-runtime";
nativeBuildInputs = { pkgs, ... }: [
pkgs.protobuf
];
buildEnv = { inputs, pkgs, system }: {
QUIXOS_PROTO_PATH = "${pkgs.protobuf}/include:${inputs.quixos-protocol.packages.${system}.default}/proto";
packageOutputs = quixosHelpers.mkCaminoTsYarnNixifyFlake {
inherit inputs nixpkgs flake-utils;
packageRoot = ./.;
sourceName = "quixos-camino-package-runtime-source";
promptName = "camino-package-runtime";
nativeBuildInputs = { pkgs, ... }: [
pkgs.protobuf
];
buildEnv =
{
inputs,
pkgs,
system,
}:
{
QUIXOS_PROTO_PATH = "${pkgs.protobuf}/include:${
inputs.quixos-protocol.packages.${system}.default
}/proto";
};
devShellPackages = { pkgs, ... }: [
pkgs.protobuf
];
devShellHook =
{
inputs,
pkgs,
system,
...
}:
''
export QUIXOS_PROTO_PATH="${pkgs.protobuf}/include:${
inputs.quixos-protocol.packages.${system}.default
}/proto''${QUIXOS_PROTO_PATH:+:$QUIXOS_PROTO_PATH}"
'';
};
devShellPackages = { pkgs, ... }: [
pkgs.protobuf
];
devShellHook = { inputs, pkgs, system, ... }: ''
export QUIXOS_PROTO_PATH="${pkgs.protobuf}/include:${inputs.quixos-protocol.packages.${system}.default}/proto''${QUIXOS_PROTO_PATH:+:$QUIXOS_PROTO_PATH}"
'';
};
in
packageOutputs
// flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
builtPackage = packageOutputs.packages.${system}.default;
in
{
checks.dist-current = pkgs.runCommand "camino-package-runtime-dist-current" { } ''
diff --recursive --unified \
${./dist} \
${builtPackage}/libexec/-quixos-camino-package-runtime/dist
touch "$out"
'';
}
);
}
+260
View File
@@ -0,0 +1,260 @@
import { create } from "@bufbuild/protobuf";
import { ValueSchema, ObjectValueSchema, type Value } from "./camino/api_pb.js";
import {
derived,
jsToProtoValue,
liveValue,
protoValueToJs,
type RuntimeContext,
type RuntimeHandler,
type DerivedHandler,
} from "./index.js";
export type { QxObjectRef } from "./references.js";
import { assertReferenceFree, referenceToWire } from "./references.js";
declare const watchBrand: unique symbol;
export type QxWatchHandle = string & { readonly [watchBrand]: true };
export type MessageBinding<T> = { encode(value: T): Value; decode(value: Value): T };
export type QxLiveValue = ReturnType<typeof liveValue>;
export type BindingValue<B> = B extends MessageBinding<infer T> ? T : never;
export type QxHandler<C, O> = (context: C) => O | Promise<O>;
export type QxDerived<C, O> = { kind: "derived"; get: QxHandler<C, O> };
export type QxSession<C> = { id: string; run<T>(work: (context: C) => Promise<T>): Promise<T>; close(): Promise<void> };
export type QxContextLifecycle<C> = { signal?: AbortSignal; openSession?: () => Promise<QxSession<C>> };
export const qxDerived = <C, O>(get: QxHandler<C, O>): QxDerived<C, O> => ({ kind: "derived", get });
/** Versioned binding ABI. This mirrors the language-neutral value IR. */
export type QxValueType =
| { kind: "builtin"; name: "unit" | "watch-handle" }
| { kind: "scalar"; name: string }
| { kind: "message"; descriptorId: string }
| { kind: "record"; fields: Record<string, QxValueType> }
| { kind: "object-ref"; expectation: unknown }
| { kind: "optional" | "list"; value: QxValueType };
export type QxOperationSpec = { id: string; inputType: QxValueType; outputType: QxValueType };
export type QxPortSpec =
| { kind: "state"; id: string; valueType: QxValueType; primitives: string[] }
| { kind: "edge"; id: string; primitives: string[] }
| { kind: "interface"; id: string; operations: Record<string, QxOperationSpec> }
| { kind: "constructor"; id: string; inputType: QxValueType };
export type QxHandlerSpec = {
receiver?: "none";
inputType: QxValueType;
outputType: QxValueType;
eventType?: QxValueType;
ports: Record<string, QxPortSpec>;
};
export type QxMessages = Record<string, MessageBinding<any>>;
// Conversion belongs at the binding boundary. It does not add orchestrator validation.
export const decodeQxValue = (type: QxValueType, value: Value | undefined, messages: QxMessages): any => {
if (type.kind === "builtin" && type.name === "unit") return null;
if (type.kind === "optional" && !value) return null;
if (!value) throw new Error("Missing QX wire value");
if (type.kind === "record") {
if (value.kind.case !== "objectValue") throw new Error("Expected QX record");
const fields = value.kind.value.fields;
if (Object.keys(fields).some((name) => !Object.hasOwn(type.fields, name)))
throw new Error("Unexpected QX record field");
return Object.fromEntries(
Object.entries(type.fields).map(([name, field]) => [name, decodeQxValue(field, fields[name], messages)]),
);
}
if (type.kind === "optional")
return value.kind.case === "nullValue" ? null : decodeQxValue(type.value, value, messages);
if (type.kind === "list") {
if (value.kind.case !== "listValue") throw new Error("Expected QX list");
return value.kind.value.values.map((entry) => decodeQxValue(type.value, entry, messages));
}
if (type.kind === "message") {
assertReferenceFree(protoValueToJs(value));
const decoded = requireMessage(messages, type.descriptorId).decode(value);
assertReferenceFree(decoded);
return decoded;
}
if (type.kind === "object-ref") {
if (value.kind.case !== "refValue") throw new Error("Expected a declared RPC object reference");
return protoValueToJs(value);
}
if (value.kind.case === "refValue") throw new Error("Reference supplied to a non-reference value");
if (type.kind === "scalar") {
if (type.name === "int64" || type.name === "uint64") {
if (value.kind.case !== "integerValue") throw new Error("Expected QX integer");
return BigInt(value.kind.value);
}
if (type.name === "bytes") {
if (value.kind.case !== "bytesValue") throw new Error("Expected QX bytes");
return value.kind.value;
}
}
return protoValueToJs(value);
};
const requireMessage = (messages: QxMessages, id: string) => {
const binding = messages[id];
if (!binding) throw new Error(`Missing message binding ${id}`);
return binding;
};
export const encodeQxValue = (type: QxValueType, value: any, messages: QxMessages): Value => {
if (type.kind === "builtin" && type.name === "unit") return jsToProtoValue(null);
if (type.kind === "record") {
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected QX record");
if (Object.keys(value).some((name) => !Object.hasOwn(type.fields, name)))
throw new Error("Unexpected QX record field");
const fields = Object.fromEntries(
Object.entries(type.fields).map(([name, field]) => {
if (!Object.hasOwn(value, name) && field.kind !== "optional")
throw new Error(`Missing QX record field ${name}`);
return [name, encodeQxValue(field, value[name] ?? (field.kind === "optional" ? null : value[name]), messages)];
}),
);
return create(ValueSchema, { kind: { case: "objectValue", value: create(ObjectValueSchema, { fields }) } });
}
if (type.kind === "optional")
return value === null ? jsToProtoValue(null) : encodeQxValue(type.value, value, messages);
if (type.kind === "list")
return jsToProtoValue(value.map((entry: unknown) => liveValue(encodeQxValue(type.value, entry, messages))));
if (type.kind === "object-ref") {
referenceToWire(value);
return jsToProtoValue(value);
}
assertReferenceFree(value);
if (type.kind === "message") {
const encoded = requireMessage(messages, type.descriptorId).encode(value);
assertReferenceFree(protoValueToJs(encoded));
return encoded;
}
return jsToProtoValue(value);
};
const inputValue = (context: RuntimeContext, type: QxValueType) => {
if (type.kind === "message" || type.kind === "record")
return create(ValueSchema, {
kind: { case: "objectValue", value: create(ObjectValueSchema, { fields: context.inputProto }) },
});
return context.inputProto.value;
};
const inputFields = (type: QxValueType, value: unknown, messages: QxMessages): Record<string, unknown> => {
if (type.kind === "builtin" && type.name === "unit") return {};
const encoded = encodeQxValue(type, value, messages);
if (type.kind === "message" || type.kind === "record") {
if (encoded.kind.case !== "objectValue") throw new Error("Message inputs must encode an object value");
return Object.fromEntries(Object.entries(encoded.kind.value.fields).map(([key, entry]) => [key, liveValue(entry)]));
}
return { value: liveValue(encoded) };
};
/** The sole unchecked cast connects generated contracts to the dynamic RPC runtime. */
export const bindQxHandler = <C, O>(
spec: QxHandlerSpec,
handler: QxHandler<C, O> | QxDerived<C, O>,
messages: QxMessages,
): RuntimeHandler | DerivedHandler => {
const bindContext = (raw: RuntimeContext): C => {
const ports = Object.fromEntries(
Object.entries(spec.ports).map(([name, port]) => {
switch (port.kind) {
case "state": {
const state = raw.state(port.id);
return [
name,
{
...(port.primitives.includes("read")
? {
get: async () => decodeQxValue(port.valueType, (await state.live()).$quixosValue, messages),
live: () => state.live(),
}
: {}),
...(port.primitives.includes("write")
? {
set: async (value: unknown) =>
state.set(liveValue(encodeQxValue(port.valueType, value, messages))),
}
: {}),
},
];
}
case "edge": {
const edge = raw.edge(port.id);
return [
name,
{
...Object.fromEntries(
port.primitives.map((primitive) => [
primitive,
edge[primitive as "resolve" | "connect" | "disconnect"],
]),
),
...(port.primitives.includes("resolve") ? { collection: edge.collection } : {}),
...(port.primitives.includes("resolve") &&
port.primitives.includes("connect") &&
port.primitives.includes("disconnect")
? { replace: edge.replace }
: {}),
},
];
}
case "interface": {
const target = raw.interface(port.id);
return [
name,
{
objectId: target.objectId,
live: Object.fromEntries(
Object.entries(port.operations).map(([name, operation]) => [
name,
(input: unknown) => target.live(operation.id, inputFields(operation.inputType, input, messages)),
]),
),
...Object.fromEntries(
Object.entries(port.operations).map(([name, operation]) => [
name,
async (input: unknown) =>
decodeQxValue(
operation.outputType,
(await target.live(operation.id, inputFields(operation.inputType, input, messages)))
.$quixosValue,
messages,
),
]),
),
},
];
}
case "constructor":
return [
name,
{
construct: (input: unknown) =>
raw.constructor(port.id).construct(inputFields(port.inputType, input, messages)),
},
];
}
}),
);
return {
...(spec.receiver === "none" ? {} : { objectId: raw.objectId }),
signal: raw.signal,
...(spec.receiver !== "none" && raw.openSession
? {
openSession: async () => {
const session = await raw.openSession!();
return {
id: session.id,
close: () => session.close(),
run: <T>(work: (context: C) => Promise<T>) => session.run((next) => work(bindContext(next))),
};
},
}
: {}),
input: decodeQxValue(spec.inputType, inputValue(raw, spec.inputType), messages),
ports,
} as C;
};
const execute = async (raw: RuntimeContext) => {
const context = bindContext(raw);
const value = await (typeof handler === "function" ? handler(context) : handler.get(context));
return liveValue(encodeQxValue(spec.eventType ?? spec.outputType, value, messages));
};
return typeof handler === "function" ? execute : derived(execute);
};
+138 -8
View File
File diff suppressed because one or more lines are too long
+42 -1
View File
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file camino/schema.proto.
*/
export const file_camino_schema: GenFile = /*@__PURE__*/
fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiQQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJIqQBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIqYBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCCKHAQoORWRnZUF0dGFjaG1lbnQSFAoMZWRnZV90eXBlX2lkGAEgASgJEhQKDGRpc3BsYXlfbmFtZRgCIAEoCRIjCgVmaXJzdBgDIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSJAoGc2Vjb25kGAQgASgLMhQuY2FtaW5vLkVkZ2VFbmRwb2ludCLsAQoPUGVyc2lzdGVuY2VQbGFuEhQKDHdvcmtzcGFjZV9pZBgBIAEoCRIdChV3b3Jrc3BhY2VfcmV2aXNpb25faWQYAiABKAkSJQoFYXRvbXMYAyADKAsyFi5jYW1pbm8uQXRvbURlZmluaXRpb24SLQoMY29uZm9ybWFuY2VzGAQgAygLMhcuY2FtaW5vLkF0b21Db25mb3JtYW5jZRInCgZzdGF0ZXMYBSADKAsyFy5jYW1pbm8uU3RhdGVBdHRhY2htZW50EiUKBWVkZ2VzGAYgAygLMhYuY2FtaW5vLkVkZ2VBdHRhY2htZW50KmgKC0NhcmRpbmFsaXR5EhsKF0NBUkRJTkFMSVRZX1VOU1BFQ0lGSUVEEAASEAoMT1BUSU9OQUxfT05FEAESDwoLRVhBQ1RMWV9PTkUQAhIICgRNQU5ZEAMSDwoLTUFOWV9VTklRVUUQBGIGcHJvdG8z");
fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiWQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJIsIBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYByABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIvsBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCBIRCglvbl9kZWxldGUYBiABKAkSFAoMcmV0YWluX290aGVyGAcgASgIEhAKCGtleV90eXBlGAggASgJEhgKEHB1YmxpY190cmF2ZXJzYWwYCSABKAgipQEKDkVkZ2VBdHRhY2htZW50EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSIwoFZmlyc3QYAyABKAsyFC5jYW1pbm8uRWRnZUVuZHBvaW50EiQKBnNlY29uZBgEIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYBSABKAki7AEKD1BlcnNpc3RlbmNlUGxhbhIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEiUKBWF0b21zGAMgAygLMhYuY2FtaW5vLkF0b21EZWZpbml0aW9uEi0KDGNvbmZvcm1hbmNlcxgEIAMoCzIXLmNhbWluby5BdG9tQ29uZm9ybWFuY2USJwoGc3RhdGVzGAUgAygLMhcuY2FtaW5vLlN0YXRlQXR0YWNobWVudBIlCgVlZGdlcxgGIAMoCzIWLmNhbWluby5FZGdlQXR0YWNobWVudCpoCgtDYXJkaW5hbGl0eRIbChdDQVJESU5BTElUWV9VTlNQRUNJRklFRBAAEhAKDE9QVElPTkFMX09ORRABEg8KC0VYQUNUTFlfT05FEAISCAoETUFOWRADEg8KC01BTllfVU5JUVVFEARiBnByb3RvMw");
/**
* @generated from message camino.AtomDefinition
@@ -47,6 +47,11 @@ export type AtomConformance = Message<"camino.AtomConformance"> & {
* @generated from field: string interface_revision_id = 2;
*/
interfaceRevisionId: string;
/**
* @generated from field: string conformance_id = 3;
*/
conformanceId: string;
};
/**
@@ -89,6 +94,11 @@ export type StateAttachment = Message<"camino.StateAttachment"> & {
* @generated from field: string default_value_json = 6;
*/
defaultValueJson: string;
/**
* @generated from field: string owner_conformance_id = 7;
*/
ownerConformanceId: string;
};
/**
@@ -155,6 +165,32 @@ export type EdgeEndpoint = Message<"camino.EdgeEndpoint"> & {
* @generated from field: bool ordered = 5;
*/
ordered: boolean;
/**
* Empty means restrict. Direction is the endpoint being deleted.
*
* @generated from field: string on_delete = 6;
*/
onDelete: string;
/**
* @generated from field: bool retain_other = 7;
*/
retainOther: boolean;
/**
* Empty for sets/lists, otherwise string, boolean, or int64 map keys.
*
* @generated from field: string key_type = 8;
*/
keyType: string;
/**
* Explicit read-only dependency injection traversal, not mutation authority.
*
* @generated from field: bool public_traversal = 9;
*/
publicTraversal: boolean;
};
/**
@@ -187,6 +223,11 @@ export type EdgeAttachment = Message<"camino.EdgeAttachment"> & {
* @generated from field: camino.EdgeEndpoint second = 4;
*/
second?: EdgeEndpoint | undefined;
/**
* @generated from field: string owner_conformance_id = 5;
*/
ownerConformanceId: string;
};
/**
+569 -207
View File
@@ -1,6 +1,26 @@
import http from "node:http";
import { readFileSync } from "node:fs";
import { createInvocationRegistry } from "./invocations.js";
import {
isObjectReference,
referenceFromWire,
referenceToWire,
assertReferenceFree,
type QxObjectRef,
} from "./references.js";
export * from "./bindings.js";
export { relationshipMap, relationshipList, relationshipSet } from "./relationships.js";
import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "node:crypto";
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
export {
createMigrationContext,
migrationObjectId,
serveMigration,
type MigrationContext,
type MigrationInput,
type MigrationOutput,
type MigrationEdge,
} from "./migration.js";
import { create, equals } from "@bufbuild/protobuf";
import { Code, ConnectError, createClient, type Client, type ConnectRouter } from "@connectrpc/connect";
import { connectNodeAdapter, createConnectTransport } from "@connectrpc/connect-node";
@@ -57,13 +77,22 @@ const base64ToBytes = (value: string) => Buffer.from(value, "base64");
const isRecord = (value: unknown): value is Record<string, unknown> =>
Boolean(value) && typeof value === "object" && !Array.isArray(value);
const isWrappedValue = (value: unknown): value is { $quixosValue: Value } =>
isRecord(value) && "$quixosValue" in value &&
isRecord(value.$quixosValue) && value.$quixosValue.$typeName === "camino.Value";
isRecord(value) &&
"$quixosValue" in value &&
isRecord(value.$quixosValue) &&
value.$quixosValue.$typeName === "camino.Value";
export const objectRef = (objectId: string) => ({ $quixosRef: objectId });
export const objectRef = (reference: QxObjectRef) => {
referenceToWire(reference);
return reference;
};
export const liveValue = (value: Value) => ({ $quixosValue: value });
export const jsToProtoValue = (value: unknown): Value => {
if (isObjectReference(value))
return create(ValueSchema, {
kind: { case: "refValue", value: create(RefValueSchema, { objectId: referenceToWire(value) }) },
});
if (isWrappedValue(value)) return value.$quixosValue;
if (value === null || value === undefined) {
return create(ValueSchema, { kind: { case: "nullValue", value: create(NullValueSchema, {}) } });
@@ -78,48 +107,56 @@ export const jsToProtoValue = (value: unknown): Value => {
kind: { case: "listValue", value: create(ListValueSchema, { values: value.map(jsToProtoValue) }) },
});
}
if (isRecord(value) && typeof value.$quixosRef === "string") {
if (isRecord(value) && "$quixosRef" in value) throw new Error("Raw ID wrappers are not object references");
if (isRecord(value) && typeof value.$quixosCrdtType === "string" && typeof value.$quixosCrdtPayload === "string") {
return create(ValueSchema, {
kind: { case: "refValue", value: create(RefValueSchema, { objectId: value.$quixosRef }) },
});
}
if (isRecord(value) && typeof value.$quixosCrdtType === "string" &&
typeof value.$quixosCrdtPayload === "string") {
return create(ValueSchema, {
kind: { case: "crdtValue", value: create(CrdtValueSchema, {
type: value.$quixosCrdtType,
encoding: typeof value.$quixosCrdtEncoding === "string" ? value.$quixosCrdtEncoding : "base64",
payload: base64ToBytes(value.$quixosCrdtPayload),
}) },
kind: {
case: "crdtValue",
value: create(CrdtValueSchema, {
type: value.$quixosCrdtType,
encoding: typeof value.$quixosCrdtEncoding === "string" ? value.$quixosCrdtEncoding : "base64",
payload: base64ToBytes(value.$quixosCrdtPayload),
}),
},
});
}
if (!isRecord(value)) throw new Error(`Unsupported runtime value ${typeof value}`);
return create(ValueSchema, {
kind: { case: "objectValue", value: create(ObjectValueSchema, {
fields: Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsToProtoValue(entry)])),
}) },
kind: {
case: "objectValue",
value: create(ObjectValueSchema, {
fields: Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, jsToProtoValue(entry)])),
}),
},
});
};
export const protoValueToJs = (value: Value | undefined): unknown => {
switch (value?.kind.case) {
case "nullValue":
case undefined: return null;
case undefined:
return null;
case "boolValue":
case "numberValue":
case "stringValue":
case "integerValue": return value.kind.value;
case "bytesValue": return bytesToBase64(value.kind.value);
case "refValue": return value.kind.value.objectId;
case "listValue": return value.kind.value.values.map(protoValueToJs);
case "objectValue": return Object.fromEntries(
Object.entries(value.kind.value.fields).map(([key, entry]) => [key, protoValueToJs(entry)]),
);
case "crdtValue": return {
$quixosCrdtType: value.kind.value.type,
$quixosCrdtEncoding: value.kind.value.encoding,
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
};
case "integerValue":
return value.kind.value;
case "bytesValue":
return bytesToBase64(value.kind.value);
case "refValue":
return referenceFromWire(value.kind.value.objectId);
case "listValue":
return value.kind.value.values.map(protoValueToJs);
case "objectValue":
return Object.fromEntries(
Object.entries(value.kind.value.fields).map(([key, entry]) => [key, protoValueToJs(entry)]),
);
case "crdtValue":
return {
$quixosCrdtType: value.kind.value.type,
$quixosCrdtEncoding: value.kind.value.encoding,
$quixosCrdtPayload: bytesToBase64(value.kind.value.payload),
};
}
};
@@ -135,21 +172,38 @@ export type StatePort<T = unknown> = {
export type EdgePort = {
edgeTypeId: string;
projectionId: string;
resolve(): Promise<string[]>;
connect(targetObjectId: string): Promise<void>;
resolve(): Promise<QxObjectRef[]>;
connect(target: QxObjectRef): Promise<void>;
disconnect(target: QxObjectRef): Promise<void>;
collection(): Promise<RelationshipCollection>;
replace(entries: RelationshipEntry[], expectedRevision: bigint): Promise<RelationshipCollection>;
};
export type RelationshipEntry<T extends QxObjectRef = QxObjectRef> = {
edgeId?: string;
target: T;
key?: string | boolean | bigint;
};
export type RelationshipCollection<T extends QxObjectRef = QxObjectRef> = {
revision: bigint;
entries: RelationshipEntry<T>[];
};
export type InterfacePort = {
objectId: QxObjectRef;
interfaceRevisionId: string;
invoke(operationId: string, input?: Record<string, unknown>): Promise<unknown>;
live(operationId: string, input?: Record<string, unknown>): Promise<ReturnType<typeof liveValue>>;
};
export type ConstructorPort = {
atomId: string;
construct(input?: Record<string, unknown>): Promise<string>;
construct(input?: Record<string, unknown>): Promise<QxObjectRef>;
};
export type RuntimePort = StatePort | EdgePort | InterfacePort | ConstructorPort;
export type RuntimeContext = {
objectId: string;
/** Cooperative cancellation. Completion is acknowledged only after the handler returns. */
signal?: AbortSignal;
openSession?: () => Promise<RuntimeSession>;
objectId: QxObjectRef;
input: Record<string, unknown>;
inputProto: Record<string, Value>;
ports: ReadonlyMap<string, RuntimePort>;
@@ -159,10 +213,25 @@ export type RuntimeContext = {
constructor(portId: string): ConstructorPort;
};
export type RuntimeSession = {
id: string;
run<T>(work: (context: RuntimeContext) => Promise<T>): Promise<T>;
/** Close the external resource first, then close its host retention/session. */
close(): Promise<void>;
};
export class RuntimeAuthorityError extends Error {
readonly retryable: boolean;
constructor(message: string) {
super(message);
this.name = "RuntimeAuthorityError";
this.retryable = /WORKSPACE_FENCED|STALE_EPOCH/.test(message);
}
}
const targetForEdge = (
edge: { firstObjectId: string; secondObjectId: string; firstProjectionId: string },
projectionId: string,
) => edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId;
) => (edge.firstProjectionId === projectionId ? edge.secondObjectId : edge.firstObjectId);
export const createRuntimeContext = (
camino: CaminoClient,
@@ -174,20 +243,22 @@ export const createRuntimeContext = (
switch (dependency.binding.case) {
case "stateSlotId": {
const slotId = dependency.binding.value;
const dependencyObjectId = dependency.objectId || request.objectId;
const state: StatePort = {
slotId,
async get() {
await recordDependency({ kind: "state", objectId: request.objectId, attachmentId: slotId });
return protoValueToJs((await camino.readState({ objectId: request.objectId, slotId })).value);
await recordDependency({ kind: "state", objectId: dependencyObjectId, attachmentId: slotId });
return protoValueToJs((await camino.readState({ objectId: dependencyObjectId, slotId })).value);
},
async live() {
await recordDependency({ kind: "state", objectId: request.objectId, attachmentId: slotId });
const value = await camino.readState({ objectId: request.objectId, slotId });
await recordDependency({ kind: "state", objectId: dependencyObjectId, attachmentId: slotId });
const value = await camino.readState({ objectId: dependencyObjectId, slotId });
if (!value.value) throw new Error(`State ${slotId} returned no value`);
return liveValue(value.value);
},
async set(value) {
await camino.writeState({ objectId: request.objectId, slotId, value: jsToProtoValue(value) });
assertReferenceFree(isWrappedValue(value) ? protoValueToJs(value.$quixosValue) : value);
await camino.writeState({ objectId: dependencyObjectId, slotId, value: jsToProtoValue(value) });
},
};
ports.set(dependency.portId, state);
@@ -195,33 +266,121 @@ export const createRuntimeContext = (
}
case "edge": {
const { edgeTypeId, projectionId } = dependency.binding.value;
const dependencyObjectId = dependency.objectId || request.objectId;
const collectionResult = (response: {
revision: bigint;
entries: { edgeId: string; targetObjectId: string; key?: Value }[];
}): RelationshipCollection => ({
revision: response.revision,
entries: response.entries.map((entry) => ({
edgeId: entry.edgeId,
target: referenceFromWire(entry.targetObjectId),
...(entry.key
? {
key:
entry.key.kind.case === "integerValue"
? BigInt(entry.key.kind.value)
: (protoValueToJs(entry.key) as string | boolean),
}
: {}),
})),
});
const edge: EdgePort = {
edgeTypeId,
projectionId,
async resolve() {
await recordDependency({ kind: "edge", objectId: request.objectId, attachmentId: edgeTypeId, projectionId });
const result = await camino.resolveEdge({ objectId: request.objectId, edgeTypeId, projectionId });
return result.edges.map((entry) => targetForEdge(entry, projectionId));
async collection() {
await recordDependency({
kind: "edge",
objectId: dependencyObjectId,
attachmentId: edgeTypeId,
projectionId,
});
return collectionResult(
await camino.readCollection({ objectId: dependencyObjectId, edgeTypeId, projectionId }),
);
},
async connect(targetObjectId) {
await camino.connectEdge({ objectId: request.objectId, edgeTypeId, projectionId, targetObjectId });
async replace(entries, expectedRevision) {
return collectionResult(
await camino.replaceCollection({
objectId: dependencyObjectId,
edgeTypeId,
projectionId,
expectedRevision,
entries: entries.map((entry) => {
assertReferenceFree(entry.key);
return {
edgeId: entry.edgeId ?? "",
targetObjectId: referenceToWire(entry.target),
key: entry.key === undefined ? undefined : jsToProtoValue(entry.key),
};
}),
}),
);
},
async resolve() {
await recordDependency({
kind: "edge",
objectId: dependencyObjectId,
attachmentId: edgeTypeId,
projectionId,
});
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
return result.edges.map((entry) => referenceFromWire(targetForEdge(entry, projectionId)));
},
async connect(target) {
const targetObjectId = referenceToWire(target);
await camino.connectEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId, targetObjectId });
},
async disconnect(target) {
const targetObjectId = referenceToWire(target);
const result = await camino.resolveEdge({ objectId: dependencyObjectId, edgeTypeId, projectionId });
for (const entry of result.edges) {
if (targetForEdge(entry, projectionId) === targetObjectId)
await camino.disconnectEdge({ edgeId: entry.id });
}
},
};
ports.set(dependency.portId, edge);
break;
}
case "receiverInterfaceRevisionId": {
case "interfaceRevisionId": {
const interfaceRevisionId = dependency.binding.value;
const dependencyObjectId = dependency.objectId || request.objectId;
const invoke = async (operationId: string, input: Record<string, unknown>) => {
const response = await orch.invokeCapability({
capability: create(CapabilityRefSchema, { interfaceRevisionId, operationId }),
objectId: dependencyObjectId,
input: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsToProtoValue(value)])),
});
if (!response.ok) throw new Error(response.error || `Capability ${operationId} failed`);
for (const dependency of response.dependencies) {
if (dependency.kind === "state") {
await recordDependency({
kind: "state",
objectId: dependency.objectId,
attachmentId: dependency.attachmentId,
});
} else if (dependency.kind === "edge") {
await recordDependency({
kind: "edge",
objectId: dependency.objectId,
attachmentId: dependency.attachmentId,
projectionId: dependency.projectionId,
});
}
}
return response.result;
};
const capability: InterfacePort = {
objectId: referenceFromWire(dependencyObjectId),
interfaceRevisionId,
async invoke(operationId, input = {}) {
const response = await orch.invokeCapability({
capability: create(CapabilityRefSchema, { interfaceRevisionId, operationId }),
objectId: request.objectId,
input: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsToProtoValue(value)])),
});
if (!response.ok) throw new Error(response.error || `Capability ${operationId} failed`);
return protoValueToJs(response.result);
return protoValueToJs(await invoke(operationId, input));
},
async live(operationId, input = {}) {
const value = await invoke(operationId, input);
if (!value) throw new Error(`Capability ${operationId} returned no value`);
return liveValue(value);
},
};
ports.set(dependency.portId, capability);
@@ -237,7 +396,7 @@ export const createRuntimeContext = (
input: Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsToProtoValue(value)])),
});
if (!response.object) throw new Error(`Constructor ${atomId} returned no object`);
return response.object.id;
return referenceFromWire(response.object.id);
},
};
ports.set(dependency.portId, constructor);
@@ -250,7 +409,9 @@ export const createRuntimeContext = (
return port as T;
};
return {
objectId: request.objectId,
get objectId() {
return referenceFromWire(request.objectId);
},
input: protoFieldsToJs(request.input),
inputProto: request.input,
ports,
@@ -273,20 +434,21 @@ const evaluate = async (
observe?: (dependency: RuntimeDependency) => Promise<void>,
) => {
const dependencies = new Map<string, RuntimeDependency>();
const result = await dependencyScope.run(
{ dependencies, observe },
() => isDerived(handler) ? handler.get(context) : handler(context),
const result = await dependencyScope.run({ dependencies, observe }, () =>
isDerived(handler) ? handler.get(context) : handler(context),
);
return { value: jsToProtoValue(result), dependencies: [...dependencies.values()] };
};
const protoDependencies = (dependencies: RuntimeDependency[]) => dependencies.map((entry) =>
create(DerivedDependencySchema, {
kind: entry.kind,
objectId: entry.objectId,
attachmentId: entry.attachmentId,
projectionId: entry.kind === "edge" ? entry.projectionId : "",
}));
const protoDependencies = (dependencies: RuntimeDependency[]) =>
dependencies.map((entry) =>
create(DerivedDependencySchema, {
kind: entry.kind,
objectId: entry.objectId,
attachmentId: entry.attachmentId,
projectionId: entry.kind === "edge" ? entry.projectionId : "",
}),
);
export const createPackageRuntimeRoutes = (config: {
packageRevisionId: string;
@@ -294,165 +456,365 @@ export const createPackageRuntimeRoutes = (config: {
caminoUrl?: string;
orchUrl?: string;
}) => {
const invocations = createInvocationRegistry();
const headers: Record<string, string> = {};
if (process.env.CAMINO_RUNTIME_AUTH_TOKEN) {
headers["x-camino-runtime-token"] = process.env.CAMINO_RUNTIME_AUTH_TOKEN;
const processToken =
process.env.CAMINO_RUNTIME_AUTH_TOKEN ??
(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE
? readFileSync(process.env.CAMINO_RUNTIME_AUTH_TOKEN_FILE, "utf8").trim()
: "");
if (processToken) {
headers["x-camino-runtime-token"] = processToken;
} else if (process.env.CAMINO_RUNTIME_AUTH_REQUIRED === "1") {
throw new Error("CAMINO_RUNTIME_AUTH_TOKEN is required");
}
const camino = createClient(CaminoService, createConnectTransport({
baseUrl: config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310",
httpVersion: "1.1",
interceptors: headers["x-camino-runtime-token"] ? [
(next) => async (request) => {
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]!);
return await next(request);
},
] : [],
}));
const orch = createClient(OrchestratorRuntime, createConnectTransport({
baseUrl: config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311",
httpVersion: "1.1",
}));
return (router: ConnectRouter) => router.service(PackageRuntime, {
handshake: () => create(HandshakeResponseSchema, {
packageRevisionId: config.packageRevisionId,
runtimeProtocolVersion: "quixos-capabilities-v1",
exportIds: Object.keys(config.exports),
const camino = createClient(
CaminoService,
createConnectTransport({
baseUrl: config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310",
httpVersion: "1.1",
interceptors: headers["x-camino-runtime-token"]
? [
(next) => async (request) => {
request.header.set("x-camino-runtime-token", headers["x-camino-runtime-token"]!);
return await next(request);
},
]
: [],
}),
invoke: async (request) => {
const exportId = request.export?.exportId;
const handler = exportId ? config.exports[exportId] : undefined;
if (!handler) throw new ConnectError(`Unknown export ${exportId ?? ""}`, Code.NotFound);
try {
const result = await evaluate(handler, createRuntimeContext(camino, orch, request));
return create(InvokeResponseSchema, { ok: true, result: result.value });
} catch (error) {
return create(InvokeResponseSchema, {
ok: false,
error: error instanceof Error ? error.message : String(error),
});
}
);
const orch = createClient(
OrchestratorRuntime,
createConnectTransport({
baseUrl: config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311",
httpVersion: "1.1",
}),
);
const authenticateInstance = (header: Headers) => {
if (!process.env.QUIXOS_RUNTIME_INSTANCE_ID) return; // standalone development ABI
const supplied = Buffer.from(header.get("x-quixos-instance-token") ?? "");
const expected = Buffer.from(processToken);
if (!expected.length || supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) {
throw new ConnectError("Invalid runtime instance credential", Code.Unauthenticated);
}
};
const clientsFor = (request: { context?: { grant: string; instanceId: string; workspaceEpoch: string } }) => {
const context = request.context;
if (
process.env.QUIXOS_RUNTIME_INSTANCE_ID &&
(!context?.grant || context.instanceId !== process.env.QUIXOS_RUNTIME_INSTANCE_ID || !context.workspaceEpoch)
) {
throw new ConnectError("Managed invocation requires an exact instance and epoch grant", Code.Unauthenticated);
}
if (!context?.grant) return { camino, orch };
const transport = (url: string) =>
createConnectTransport({
baseUrl: url,
httpVersion: "1.1",
interceptors: [
(next) => async (call) => {
call.header.set("x-quixos-invocation-grant", context.grant);
call.header.set("x-camino-runtime-token", processToken);
return next(call);
},
],
});
return {
camino: createClient(
CaminoService,
transport(config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310"),
),
orch: createClient(
OrchestratorRuntime,
transport(config.orchUrl ?? process.env.QUIXOS_ORCH_URL ?? "http://127.0.0.1:7311"),
),
};
};
const runtimeControl = async <T>(operation: string, input: unknown): Promise<T> => {
const response = await fetch(
`${config.caminoUrl ?? process.env.CAMINO_URL ?? "http://127.0.0.1:7310"}/__runtime/${operation}`,
{
method: "POST",
headers: { "content-type": "application/json", "x-camino-runtime-token": processToken },
body: JSON.stringify(input),
signal: AbortSignal.timeout(10_000),
},
);
const value = (await response.json()) as T & { error?: string };
if (!response.ok) throw new RuntimeAuthorityError(value.error ?? "Runtime authority request failed");
return value;
};
const attachSessions = (
runtimeContext: RuntimeContext,
request: RuntimeRequest & {
context?: { grant: string; instanceId: string; workspaceEpoch: string; ownerConformanceId?: string };
},
watch: async function* (request, context) {
const exportId = request.export?.exportId;
const handler = exportId ? config.exports[exportId] : undefined;
if (!handler || !isDerived(handler)) {
throw new ConnectError(`Export ${exportId ?? ""} is not derived`, Code.FailedPrecondition);
}
const watchId = `watch:${randomUUID()}`;
const runtimeContext = createRuntimeContext(camino, orch, request);
type WatchOutcome = { key: string; done: boolean; error?: unknown };
type Subscription = {
dependency: RuntimeDependency;
controller: AbortController;
next: Promise<WatchOutcome>;
waitNext: () => Promise<WatchOutcome>;
) => {
if (!request.context?.grant || !request.context.ownerConformanceId) return;
runtimeContext.openSession = async () => {
const ownerId = request.context!.ownerConformanceId!;
const registration = {
grant: request.context!.grant,
objectId: request.objectId,
ownerId,
sessionId: `session:${randomBytes(16).toString("hex")}`,
token: randomBytes(32).toString("base64url"),
};
const subscriptions = new Map<string, Subscription>();
const establishing = new Map<string, Promise<void>>();
const ensureSubscription = async (dependency: RuntimeDependency) => {
const key = dependencyKey(dependency);
if (subscriptions.has(key)) return;
const pending = establishing.get(key);
if (pending) return await pending;
const establish = (async () => {
const controller = new AbortController();
const stream = camino.watchObject(
{ objectId: dependency.objectId, includeSnapshot: true },
{ signal: controller.signal },
)[Symbol.asyncIterator]();
const register = () => runtimeControl<{ sessionId: string; token: string }>("register-session", registration);
const registered = await register().catch((error) => {
// Retry a transport/lost-response failure with exactly the same identity.
// Admission/authority errors are definitive and must not be retried here.
if (error instanceof RuntimeAuthorityError) throw error;
return register();
});
let closed = false;
return {
id: registered.sessionId,
async run(work) {
if (closed) throw new RuntimeAuthorityError("SESSION_CLOSED");
// Acquisition happens before user code. A fence failure can be retried
// by the caller without replaying a side-effecting callback.
const grant = await runtimeControl<{
grant: string;
epoch: string;
instanceId: string;
invocationId: string;
bindingDigest: string;
}>("acquire-session", registered);
const execution = invocations.begin(grant.invocationId);
const sessionRequest = {
...request,
context: { grant: grant.grant, instanceId: grant.instanceId, workspaceEpoch: grant.epoch },
};
const clients = clientsFor(sessionRequest);
const context = createRuntimeContext(clients.camino, clients.orch, sessionRequest);
context.signal = execution.signal;
try {
// Camino subscribes before producing the snapshot, so once this
// resolves the following state/edge read cannot race the stream.
const snapshot = await stream.next();
if (snapshot.done) throw new Error(`Dependency stream ${key} ended during setup`);
const waitNext = () => stream.next().then(
(result) => ({ key, done: Boolean(result.done) }),
(error: unknown) => ({ key, done: true, error }),
return await work(context);
} finally {
execution.finish();
await runtimeControl("complete-invocation", { invocationId: grant.invocationId }).catch((error) =>
console.error("Session completion will be reconciled by the host", error),
);
const subscription: Subscription = {
dependency,
controller,
waitNext,
next: Promise.resolve({ key, done: false }),
};
subscription.next = waitNext();
subscriptions.set(key, subscription);
} catch (error) {
controller.abort();
throw error;
}
})();
establishing.set(key, establish);
},
async close() {
await runtimeControl("close-session", registered);
closed = true;
},
};
};
};
return (router: ConnectRouter) =>
router.service(PackageRuntime, {
handshake: (request) =>
create(HandshakeResponseSchema, {
packageRevisionId: config.packageRevisionId,
runtimeProtocolVersion: "quixos-capabilities-v1",
exportIds: Object.keys(config.exports),
capabilities: ["invocation-completion-v1", "instance-authentication-v1", "epoch-grants-v1"],
instanceId: process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
authenticationProof:
request.nonce && processToken
? createHmac("sha256", processToken)
.update(
JSON.stringify([
request.nonce,
process.env.QUIXOS_RUNTIME_INSTANCE_ID ?? "",
config.packageRevisionId,
]),
)
.digest("hex")
: "",
}),
getInvocationStatus: (request, context) => {
authenticateInstance(context.requestHeader);
return invocations.status(request.invocationId);
},
cancelInvocation: (request, context) => {
authenticateInstance(context.requestHeader);
return invocations.cancel(request.invocationId);
},
invoke: async (request, context) => {
authenticateInstance(context.requestHeader);
const { camino, orch } = clientsFor(request);
const exportId = request.export?.exportId;
const handler = exportId ? config.exports[exportId] : undefined;
if (!handler) throw new ConnectError(`Unknown export ${exportId ?? ""}`, Code.NotFound);
const execution = invocations.begin(
request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()),
);
try {
await establish;
} finally {
establishing.delete(key);
const runtimeContext = createRuntimeContext(camino, orch, request);
attachSessions(runtimeContext, request);
runtimeContext.signal = AbortSignal.any([context.signal, execution.signal]);
const result = await evaluate(handler, runtimeContext);
execution.finish();
return create(InvokeResponseSchema, {
ok: true,
result: result.value,
dependencies: protoDependencies(result.dependencies),
});
} catch (error) {
execution.finish(true);
return create(InvokeResponseSchema, {
ok: false,
error: error instanceof Error ? error.message : String(error),
});
}
};
const abortAll = () => {
for (const subscription of subscriptions.values()) {
subscription.controller.abort();
},
watch: async function* (request, context) {
authenticateInstance(context.requestHeader);
const { camino, orch } = clientsFor(request);
const exportId = request.export?.exportId;
const handler = exportId ? config.exports[exportId] : undefined;
if (!handler || !isDerived(handler)) {
throw new ConnectError(`Export ${exportId ?? ""} is not derived`, Code.FailedPrecondition);
}
};
context.signal.addEventListener("abort", abortAll, { once: true });
const execution = invocations.begin(
request.invocationId || (process.env.QUIXOS_RUNTIME_INSTANCE_ID ? "" : randomUUID()),
);
const signal = AbortSignal.any([context.signal, execution.signal]);
try {
const watchId = `watch:${randomUUID()}`;
const runtimeContext = createRuntimeContext(camino, orch, request);
attachSessions(runtimeContext, request);
runtimeContext.signal = signal;
type WatchOutcome = { key: string; done: boolean; error?: unknown };
type Subscription = {
dependency: RuntimeDependency;
controller: AbortController;
next: Promise<WatchOutcome>;
waitNext: () => Promise<WatchOutcome>;
};
const subscriptions = new Map<string, Subscription>();
const establishing = new Map<string, Promise<void>>();
let subscriptionEpoch = 0;
let current = await evaluate(handler, runtimeContext, ensureSubscription);
yield create(WatchEventSchema, {
watchId,
value: current.value,
dependencies: protoDependencies(current.dependencies),
initial: true,
});
const abort = new Promise<"abort">((resolve) => {
if (context.signal.aborted) resolve("abort");
else context.signal.addEventListener("abort", () => resolve("abort"), { once: true });
});
try {
while (!context.signal.aborted) {
if (subscriptions.size === 0) {
await abort;
break;
}
const outcome = await Promise.race([
...[...subscriptions.values()].map((entry) => entry.next),
abort,
]);
if (outcome === "abort") break;
const subscription = subscriptions.get(outcome.key);
if (!subscription) continue;
if (outcome.error) throw outcome.error;
if (outcome.done) throw new Error(`Dependency stream ${outcome.key} ended unexpectedly`);
subscription.next = subscription.waitNext();
const updated = await evaluate(handler, runtimeContext, ensureSubscription);
const active = new Set(updated.dependencies.map(dependencyKey));
for (const [key, entry] of subscriptions) {
if (!active.has(key)) {
entry.controller.abort();
subscriptions.delete(key);
const ensureSubscription = async (dependency: RuntimeDependency) => {
const key = dependencyKey(dependency);
if (subscriptions.has(key)) return;
const pending = establishing.get(key);
if (pending) return await pending;
const establish = (async () => {
const controller = new AbortController();
const stream = camino
.watchObject(
{
objectId: dependency.objectId,
includeSnapshot: true,
attachmentIds: request.context?.grant ? [dependency.attachmentId] : [],
},
{ signal: controller.signal },
)
[Symbol.asyncIterator]();
try {
// Camino subscribes before producing the snapshot, so once this
// resolves the following state/edge read cannot race the stream.
const snapshot = await stream.next();
if (snapshot.done) throw new Error(`Dependency stream ${key} ended during setup`);
const waitNext = () =>
stream.next().then(
(result) => ({ key, done: Boolean(result.done) }),
(error: unknown) => ({ key, done: true, error }),
);
const subscription: Subscription = {
dependency,
controller,
waitNext,
next: Promise.resolve({ key, done: false }),
};
subscription.next = waitNext();
subscriptions.set(key, subscription);
subscriptionEpoch += 1;
} catch (error) {
controller.abort();
throw error;
}
})();
establishing.set(key, establish);
try {
await establish;
} finally {
establishing.delete(key);
}
}
if (!equals(ValueSchema, current.value, updated.value)) {
};
const abortAll = () => {
for (const subscription of subscriptions.values()) {
subscription.controller.abort();
}
};
signal.addEventListener("abort", abortAll, { once: true });
const evaluateWithStableSubscriptions = async () => {
// A direct state/edge port records its dependency before reading it,
// but a nested interface invocation can only report its transitive
// dependencies after that invocation returns. Once a new dependency
// stream is established, evaluate again so every read contributing to
// the emitted value happened after its stream became live.
for (let pass = 0; pass < 32; pass += 1) {
const before = subscriptionEpoch;
const result = await evaluate(handler, runtimeContext, ensureSubscription);
if (subscriptionEpoch === before) return result;
}
throw new Error("Derived dependency discovery did not stabilize after 32 passes");
};
try {
let current = await evaluateWithStableSubscriptions();
yield create(WatchEventSchema, {
watchId,
value: updated.value,
dependencies: protoDependencies(updated.dependencies),
value: current.value,
dependencies: protoDependencies(current.dependencies),
initial: true,
});
const abort = new Promise<"abort">((resolve) => {
if (signal.aborted) resolve("abort");
else signal.addEventListener("abort", () => resolve("abort"), { once: true });
});
while (!signal.aborted) {
if (subscriptions.size === 0) {
await abort;
break;
}
const outcome = await Promise.race([...[...subscriptions.values()].map((entry) => entry.next), abort]);
if (outcome === "abort") break;
const subscription = subscriptions.get(outcome.key);
if (!subscription) continue;
if (outcome.error) throw outcome.error;
if (outcome.done) throw new Error(`Dependency stream ${outcome.key} ended unexpectedly`);
subscription.next = subscription.waitNext();
const updated = await evaluateWithStableSubscriptions();
const active = new Set(updated.dependencies.map(dependencyKey));
for (const [key, entry] of subscriptions) {
if (!active.has(key)) {
entry.controller.abort();
subscriptions.delete(key);
}
}
if (!equals(ValueSchema, current.value, updated.value)) {
yield create(WatchEventSchema, {
watchId,
value: updated.value,
dependencies: protoDependencies(updated.dependencies),
});
}
current = updated;
}
} finally {
signal.removeEventListener("abort", abortAll);
abortAll();
}
current = updated;
} finally {
execution.finish();
}
} finally {
context.signal.removeEventListener("abort", abortAll);
abortAll();
}
},
});
},
});
};
export const servePackageRuntime = (config: {
+32
View File
@@ -0,0 +1,32 @@
/** Execution completion, not HTTP disconnection, is the drain boundary. */
export const createInvocationRegistry = () => {
const entries = new Map<string, { state: string; controller: AbortController }>();
return {
begin(id: string) {
if (!id || entries.has(id))
throw new Error("Invocation ID is missing or already used; invocations are never replayed implicitly");
// Completed identities remain until process retirement. A bounded process
// may reject new work; it must not evict and accidentally replay a call.
if (entries.size >= 100_000) throw new Error("Invocation registry full; explicit runtime retirement required");
const entry = { state: "running", controller: new AbortController() };
entries.set(id, entry);
return {
signal: entry.controller.signal,
finish(failed = false) {
entry.state = failed ? "failed" : "completed";
},
};
},
status(id: string) {
return { invocationId: id, state: entries.get(id)?.state ?? "unknown" };
},
cancel(id: string) {
const entry = entries.get(id);
if (entry && ["running", "cancellation-requested"].includes(entry.state)) {
entry.state = "cancellation-requested";
entry.controller.abort();
}
return this.status(id);
},
};
};
+154
View File
@@ -0,0 +1,154 @@
import { createHash } from "node:crypto";
export type MigrationInput = {
schemaVersion: 1;
executionId: string;
exportId: string;
ports: {
name: string;
binding: string;
view: "old" | "new";
access: ("read" | "write" | "create" | "edge")[];
atomId?: string;
attachedAtomId?: string;
defaultValue?: unknown;
states?: { objectId: string; value: unknown }[];
edges?: MigrationEdge[];
}[];
};
export type MigrationEdge = {
id: string;
edgeTypeId: string;
firstObjectId: string;
secondObjectId: string;
firstProjectionId: string;
secondProjectionId: string;
firstOrdinal?: number;
secondOrdinal?: number;
firstKeyJson?: string;
secondKeyJson?: string;
};
export type MigrationOutput = {
schemaVersion: 1;
executionId: string;
writes: { port: string; objectId: string; value: unknown }[];
creates: { port: string; logicalKey: string; objectId: string }[];
edgeReplacements: { port: string; edges: MigrationEdge[] }[];
};
export type MigrationContext = {
enumerate(port: string): { objectId: string; value: unknown }[];
read(port: string, objectId: string): unknown;
write(port: string, objectId: string, value: unknown): void;
create(port: string, logicalKey: string): string;
edges(port: string): MigrationEdge[];
replaceEdges(port: string, edges: MigrationEdge[]): void;
};
export const migrationObjectId = (executionId: string, port: string, logicalKey: string) =>
`obj:migration:${createHash("sha256")
.update(JSON.stringify([executionId, port, logicalKey]))
.digest("hex")}`;
/** No ordinary RuntimeContext or network/database clients are supplied here.
* Process isolation belongs to the host, not this convenience API. */
export const createMigrationContext = (input: MigrationInput) => {
if (
input.schemaVersion !== 1 ||
!input.executionId ||
new Set(input.ports.map((entry) => entry.name)).size !== input.ports.length
)
throw new Error("Invalid migration input");
const output: MigrationOutput = {
schemaVersion: 1,
executionId: input.executionId,
writes: [],
creates: [],
edgeReplacements: [],
};
const port = (name: string, access: string) => {
const selected = input.ports.find((entry) => entry.name === name);
if (!selected?.access.includes(access as "read") || (selected.view === "old" && access !== "read"))
throw new Error(`Migration port ${name} does not grant ${access}`);
return selected;
};
const context: MigrationContext = {
enumerate(name) {
const selected = port(name, "read"),
states = structuredClone(selected.states ?? []);
if (selected.view === "new" && Object.hasOwn(selected, "defaultValue"))
for (const helper of output.creates) {
if (
input.ports.find((entry) => entry.name === helper.port)?.atomId === selected.attachedAtomId &&
!states.some((entry) => entry.objectId === helper.objectId)
)
states.push({ objectId: helper.objectId, value: structuredClone(selected.defaultValue) });
}
if (selected.view === "new")
for (const write of output.writes) {
if (input.ports.find((entry) => entry.name === write.port)?.binding !== selected.binding) continue;
const existing = states.findIndex((entry) => entry.objectId === write.objectId),
entry = { objectId: write.objectId, value: structuredClone(write.value) };
if (existing < 0) states.push(entry);
else states[existing] = entry;
}
return states.sort((a, b) => (a.objectId < b.objectId ? -1 : a.objectId > b.objectId ? 1 : 0));
},
read(name, objectId) {
return context.enumerate(name).find((entry) => entry.objectId === objectId)?.value;
},
write(name, objectId, value) {
port(name, "write");
const previous = output.writes.findIndex((entry) => entry.port === name && entry.objectId === objectId);
const entry = { port: name, objectId, value: structuredClone(value) };
if (previous < 0) output.writes.push(entry);
else output.writes[previous] = entry;
},
create(name, logicalKey) {
port(name, "create");
if (!logicalKey || logicalKey.length > 1024)
throw new Error("Migration creation requires a bounded stable logical key");
const objectId = migrationObjectId(input.executionId, name, logicalKey);
if (!output.creates.some((entry) => entry.objectId === objectId))
output.creates.push({ port: name, logicalKey, objectId });
return objectId;
},
edges(name) {
const selected = port(name, "read");
const replacement =
selected.view === "new"
? output.edgeReplacements.find(
(entry) => input.ports.find((candidate) => candidate.name === entry.port)?.binding === selected.binding,
)
: undefined;
return structuredClone(replacement?.edges ?? selected.edges ?? []);
},
replaceEdges(name, edges) {
port(name, "edge");
const previous = output.edgeReplacements.findIndex((entry) => entry.port === name);
const entry = { port: name, edges: structuredClone(edges) };
if (previous < 0) output.edgeReplacements.push(entry);
else output.edgeReplacements[previous] = entry;
},
};
return { context, result: () => structuredClone(output) };
};
/** Entrypoint for an immutable package's dedicated bin/migrate executable.
* stdout is protocol-only; send diagnostics to stderr. The host independently
* validates every write, helper identity, contract, and completion receipt. */
export const serveMigration = async (exports: Record<string, (context: MigrationContext) => void | Promise<void>>) => {
const chunks: Buffer[] = [];
let bytes = 0;
for await (const chunk of process.stdin) {
bytes += chunk.length;
if (bytes > 16 * 1024 * 1024) throw new Error("Migration input exceeds 16 MiB");
chunks.push(Buffer.from(chunk));
}
const input = JSON.parse(Buffer.concat(chunks).toString("utf8")) as MigrationInput;
const implementation = Object.hasOwn(exports, input.exportId) ? exports[input.exportId] : undefined;
if (!implementation) throw new Error("Unknown migration export");
const execution = createMigrationContext(input);
await implementation(execution.context);
const result = JSON.stringify(execution.result());
if (Buffer.byteLength(result) > 16 * 1024 * 1024) throw new Error("Migration output exceeds 16 MiB");
process.stdout.write(`${result}\n`);
};
+351 -18
View File
File diff suppressed because one or more lines are too long
+11 -3
View File
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file quixos/refs.proto.
*/
export const file_quixos_refs: GenFile = /*@__PURE__*/
fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zIkQKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJIroBChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEigKHnJlY2VpdmVyX2ludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIAEIJCgdiaW5kaW5nIj0KDkVkZ2VEZXBlbmRlbmN5EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIVCg1wcm9qZWN0aW9uX2lkGAIgASgJYgZwcm90bzM");
fileDesc("ChFxdWl4b3MvcmVmcy5wcm90bxIGcXVpeG9zIkQKDUNhcGFiaWxpdHlSZWYSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAEgASgJEhQKDG9wZXJhdGlvbl9pZBgCIAEoCSJCChBQYWNrYWdlRXhwb3J0UmVmEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSEQoJZXhwb3J0X2lkGAIgASgJIsQBChJJbmplY3RlZERlcGVuZGVuY3kSDwoHcG9ydF9pZBgBIAEoCRIXCg1zdGF0ZV9zbG90X2lkGAIgASgJSAASJgoEZWRnZRgDIAEoCzIWLnF1aXhvcy5FZGdlRGVwZW5kZW5jeUgAEh8KFWludGVyZmFjZV9yZXZpc2lvbl9pZBgEIAEoCUgAEh0KE2NvbnN0cnVjdG9yX2F0b21faWQYBSABKAlIABIRCglvYmplY3RfaWQYBiABKAlCCQoHYmluZGluZyI9Cg5FZGdlRGVwZW5kZW5jeRIUCgxlZGdlX3R5cGVfaWQYASABKAkSFQoNcHJvamVjdGlvbl9pZBgCIAEoCWIGcHJvdG8z");
/**
* @generated from message quixos.CapabilityRef
@@ -82,10 +82,10 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
case: "edge";
} | {
/**
* @generated from field: string receiver_interface_revision_id = 4;
* @generated from field: string interface_revision_id = 4;
*/
value: string;
case: "receiverInterfaceRevisionId";
case: "interfaceRevisionId";
} | {
/**
* @generated from field: string constructor_atom_id = 5;
@@ -93,6 +93,14 @@ export type InjectedDependency = Message<"quixos.InjectedDependency"> & {
value: string;
case: "constructorAtomId";
} | { case: undefined; value?: undefined };
/**
* Defaults to the invocation receiver. A checked dependency traversal can
* select a related object explicitly before the package runtime starts.
*
* @generated from field: string object_id = 6;
*/
objectId: string;
};
/**
+142 -6
View File
@@ -14,7 +14,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file quixos/runtime.proto.
*/
export const file_quixos_runtime: GenFile = /*@__PURE__*/
fileDesc("ChRxdWl4b3MvcnVudGltZS5wcm90bxIOcXVpeG9zLnJ1bnRpbWUiMQoQSGFuZHNoYWtlUmVxdWVzdBIdChVvcmNoX3Byb3RvY29sX3ZlcnNpb24YASABKAkiZgoRSGFuZHNoYWtlUmVzcG9uc2USGwoTcGFja2FnZV9yZXZpc2lvbl9pZBgBIAEoCRIgChhydW50aW1lX3Byb3RvY29sX3ZlcnNpb24YAiABKAkSEgoKZXhwb3J0X2lkcxgDIAMoCSKLAgoNSW52b2tlUmVxdWVzdBIVCg1pbnZvY2F0aW9uX2lkGAEgASgJEigKBmV4cG9ydBgCIAEoCzIYLnF1aXhvcy5QYWNrYWdlRXhwb3J0UmVmEhEKCW9iamVjdF9pZBgDIAEoCRI3CgVpbnB1dBgEIAMoCzIoLnF1aXhvcy5ydW50aW1lLkludm9rZVJlcXVlc3QuSW5wdXRFbnRyeRIwCgxkZXBlbmRlbmNpZXMYBSADKAsyGi5xdWl4b3MuSW5qZWN0ZWREZXBlbmRlbmN5GjsKCklucHV0RW50cnkSCwoDa2V5GAEgASgJEhwKBXZhbHVlGAIgASgLMg0uY2FtaW5vLlZhbHVlOgI4ASJKCg5JbnZva2VSZXNwb25zZRIKCgJvaxgBIAEoCBIdCgZyZXN1bHQYAiABKAsyDS5jYW1pbm8uVmFsdWUSDQoFZXJyb3IYAyABKAkiiQIKDFdhdGNoUmVxdWVzdBIVCg1pbnZvY2F0aW9uX2lkGAEgASgJEigKBmV4cG9ydBgCIAEoCzIYLnF1aXhvcy5QYWNrYWdlRXhwb3J0UmVmEhEKCW9iamVjdF9pZBgDIAEoCRI2CgVpbnB1dBgEIAMoCzInLnF1aXhvcy5ydW50aW1lLldhdGNoUmVxdWVzdC5JbnB1dEVudHJ5EjAKDGRlcGVuZGVuY2llcxgFIAMoCzIaLnF1aXhvcy5JbmplY3RlZERlcGVuZGVuY3kaOwoKSW5wdXRFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBImIKEURlcml2ZWREZXBlbmRlbmN5EgwKBGtpbmQYASABKAkSEQoJb2JqZWN0X2lkGAIgASgJEhUKDWF0dGFjaG1lbnRfaWQYAyABKAkSFQoNcHJvamVjdGlvbl9pZBgEIAEoCSKVAQoKV2F0Y2hFdmVudBIQCgh3YXRjaF9pZBgBIAEoCRIcCgV2YWx1ZRgCIAEoCzINLmNhbWluby5WYWx1ZRI3CgxkZXBlbmRlbmNpZXMYAyADKAsyIS5xdWl4b3MucnVudGltZS5EZXJpdmVkRGVwZW5kZW5jeRINCgVlcnJvchgEIAEoCRIPCgdpbml0aWFsGAUgASgIMvABCg5QYWNrYWdlUnVudGltZRJQCglIYW5kc2hha2USIC5xdWl4b3MucnVudGltZS5IYW5kc2hha2VSZXF1ZXN0GiEucXVpeG9zLnJ1bnRpbWUuSGFuZHNoYWtlUmVzcG9uc2USRwoGSW52b2tlEh0ucXVpeG9zLnJ1bnRpbWUuSW52b2tlUmVxdWVzdBoeLnF1aXhvcy5ydW50aW1lLkludm9rZVJlc3BvbnNlEkMKBVdhdGNoEhwucXVpeG9zLnJ1bnRpbWUuV2F0Y2hSZXF1ZXN0GhoucXVpeG9zLnJ1bnRpbWUuV2F0Y2hFdmVudDABYgZwcm90bzM", [file_camino_api, file_quixos_refs]);
fileDesc("ChRxdWl4b3MvcnVudGltZS5wcm90bxIOcXVpeG9zLnJ1bnRpbWUiQAoQSGFuZHNoYWtlUmVxdWVzdBIdChVvcmNoX3Byb3RvY29sX3ZlcnNpb24YASABKAkSDQoFbm9uY2UYAiABKAkirwEKEUhhbmRzaGFrZVJlc3BvbnNlEhsKE3BhY2thZ2VfcmV2aXNpb25faWQYASABKAkSIAoYcnVudGltZV9wcm90b2NvbF92ZXJzaW9uGAIgASgJEhIKCmV4cG9ydF9pZHMYAyADKAkSEwoLaW5zdGFuY2VfaWQYBCABKAkSHAoUYXV0aGVudGljYXRpb25fcHJvb2YYBSABKAkSFAoMY2FwYWJpbGl0aWVzGAYgAygJIpoBChFJbnZvY2F0aW9uQ29udGV4dBIXCg93b3Jrc3BhY2VfZXBvY2gYASABKAkSEwoLaW5zdGFuY2VfaWQYAiABKAkSFgoOYmluZGluZ19kaWdlc3QYAyABKAkSDQoFZ3JhbnQYBCABKAkSEgoKc2Vzc2lvbl9pZBgFIAEoCRIcChRvd25lcl9jb25mb3JtYW5jZV9pZBgGIAEoCSIxChhJbnZvY2F0aW9uQ29udHJvbFJlcXVlc3QSFQoNaW52b2NhdGlvbl9pZBgBIAEoCSI4ChBJbnZvY2F0aW9uU3RhdHVzEhUKDWludm9jYXRpb25faWQYASABKAkSDQoFc3RhdGUYAiABKAkivwIKDUludm9rZVJlcXVlc3QSFQoNaW52b2NhdGlvbl9pZBgBIAEoCRIoCgZleHBvcnQYAiABKAsyGC5xdWl4b3MuUGFja2FnZUV4cG9ydFJlZhIRCglvYmplY3RfaWQYAyABKAkSNwoFaW5wdXQYBCADKAsyKC5xdWl4b3MucnVudGltZS5JbnZva2VSZXF1ZXN0LklucHV0RW50cnkSMAoMZGVwZW5kZW5jaWVzGAUgAygLMhoucXVpeG9zLkluamVjdGVkRGVwZW5kZW5jeRIyCgdjb250ZXh0GAYgASgLMiEucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvbkNvbnRleHQaOwoKSW5wdXRFbnRyeRILCgNrZXkYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWU6AjgBIoMBCg5JbnZva2VSZXNwb25zZRIKCgJvaxgBIAEoCBIdCgZyZXN1bHQYAiABKAsyDS5jYW1pbm8uVmFsdWUSDQoFZXJyb3IYAyABKAkSNwoMZGVwZW5kZW5jaWVzGAQgAygLMiEucXVpeG9zLnJ1bnRpbWUuRGVyaXZlZERlcGVuZGVuY3kivQIKDFdhdGNoUmVxdWVzdBIVCg1pbnZvY2F0aW9uX2lkGAEgASgJEigKBmV4cG9ydBgCIAEoCzIYLnF1aXhvcy5QYWNrYWdlRXhwb3J0UmVmEhEKCW9iamVjdF9pZBgDIAEoCRI2CgVpbnB1dBgEIAMoCzInLnF1aXhvcy5ydW50aW1lLldhdGNoUmVxdWVzdC5JbnB1dEVudHJ5EjAKDGRlcGVuZGVuY2llcxgFIAMoCzIaLnF1aXhvcy5JbmplY3RlZERlcGVuZGVuY3kSMgoHY29udGV4dBgGIAEoCzIhLnF1aXhvcy5ydW50aW1lLkludm9jYXRpb25Db250ZXh0GjsKCklucHV0RW50cnkSCwoDa2V5GAEgASgJEhwKBXZhbHVlGAIgASgLMg0uY2FtaW5vLlZhbHVlOgI4ASJiChFEZXJpdmVkRGVwZW5kZW5jeRIMCgRraW5kGAEgASgJEhEKCW9iamVjdF9pZBgCIAEoCRIVCg1hdHRhY2htZW50X2lkGAMgASgJEhUKDXByb2plY3Rpb25faWQYBCABKAkilQEKCldhdGNoRXZlbnQSEAoId2F0Y2hfaWQYASABKAkSHAoFdmFsdWUYAiABKAsyDS5jYW1pbm8uVmFsdWUSNwoMZGVwZW5kZW5jaWVzGAMgAygLMiEucXVpeG9zLnJ1bnRpbWUuRGVyaXZlZERlcGVuZGVuY3kSDQoFZXJyb3IYBCABKAkSDwoHaW5pdGlhbBgFIAEoCDKzAwoOUGFja2FnZVJ1bnRpbWUSUAoJSGFuZHNoYWtlEiAucXVpeG9zLnJ1bnRpbWUuSGFuZHNoYWtlUmVxdWVzdBohLnF1aXhvcy5ydW50aW1lLkhhbmRzaGFrZVJlc3BvbnNlEkcKBkludm9rZRIdLnF1aXhvcy5ydW50aW1lLkludm9rZVJlcXVlc3QaHi5xdWl4b3MucnVudGltZS5JbnZva2VSZXNwb25zZRJDCgVXYXRjaBIcLnF1aXhvcy5ydW50aW1lLldhdGNoUmVxdWVzdBoaLnF1aXhvcy5ydW50aW1lLldhdGNoRXZlbnQwARJhChNHZXRJbnZvY2F0aW9uU3RhdHVzEigucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvbkNvbnRyb2xSZXF1ZXN0GiAucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvblN0YXR1cxJeChBDYW5jZWxJbnZvY2F0aW9uEigucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvbkNvbnRyb2xSZXF1ZXN0GiAucXVpeG9zLnJ1bnRpbWUuSW52b2NhdGlvblN0YXR1c2IGcHJvdG8z", [file_camino_api, file_quixos_refs]);
/**
* @generated from message quixos.runtime.HandshakeRequest
@@ -24,6 +24,11 @@ export type HandshakeRequest = Message<"quixos.runtime.HandshakeRequest"> & {
* @generated from field: string orch_protocol_version = 1;
*/
orchProtocolVersion: string;
/**
* @generated from field: string nonce = 2;
*/
nonce: string;
};
/**
@@ -51,6 +56,21 @@ export type HandshakeResponse = Message<"quixos.runtime.HandshakeResponse"> & {
* @generated from field: repeated string export_ids = 3;
*/
exportIds: string[];
/**
* @generated from field: string instance_id = 4;
*/
instanceId: string;
/**
* @generated from field: string authentication_proof = 5;
*/
authenticationProof: string;
/**
* @generated from field: repeated string capabilities = 6;
*/
capabilities: string[];
};
/**
@@ -60,6 +80,91 @@ export type HandshakeResponse = Message<"quixos.runtime.HandshakeResponse"> & {
export const HandshakeResponseSchema: GenMessage<HandshakeResponse> = /*@__PURE__*/
messageDesc(file_quixos_runtime, 1);
/**
* @generated from message quixos.runtime.InvocationContext
*/
export type InvocationContext = Message<"quixos.runtime.InvocationContext"> & {
/**
* @generated from field: string workspace_epoch = 1;
*/
workspaceEpoch: string;
/**
* @generated from field: string instance_id = 2;
*/
instanceId: string;
/**
* @generated from field: string binding_digest = 3;
*/
bindingDigest: string;
/**
* @generated from field: string grant = 4;
*/
grant: string;
/**
* @generated from field: string session_id = 5;
*/
sessionId: string;
/**
* Host-selected owner; packages must not invent workspace-local ownership.
*
* @generated from field: string owner_conformance_id = 6;
*/
ownerConformanceId: string;
};
/**
* Describes the message quixos.runtime.InvocationContext.
* Use `create(InvocationContextSchema)` to create a new message.
*/
export const InvocationContextSchema: GenMessage<InvocationContext> = /*@__PURE__*/
messageDesc(file_quixos_runtime, 2);
/**
* @generated from message quixos.runtime.InvocationControlRequest
*/
export type InvocationControlRequest = Message<"quixos.runtime.InvocationControlRequest"> & {
/**
* @generated from field: string invocation_id = 1;
*/
invocationId: string;
};
/**
* Describes the message quixos.runtime.InvocationControlRequest.
* Use `create(InvocationControlRequestSchema)` to create a new message.
*/
export const InvocationControlRequestSchema: GenMessage<InvocationControlRequest> = /*@__PURE__*/
messageDesc(file_quixos_runtime, 3);
/**
* @generated from message quixos.runtime.InvocationStatus
*/
export type InvocationStatus = Message<"quixos.runtime.InvocationStatus"> & {
/**
* @generated from field: string invocation_id = 1;
*/
invocationId: string;
/**
* unknown, running, cancellation-requested, completed, failed
*
* @generated from field: string state = 2;
*/
state: string;
};
/**
* Describes the message quixos.runtime.InvocationStatus.
* Use `create(InvocationStatusSchema)` to create a new message.
*/
export const InvocationStatusSchema: GenMessage<InvocationStatus> = /*@__PURE__*/
messageDesc(file_quixos_runtime, 4);
/**
* @generated from message quixos.runtime.InvokeRequest
*/
@@ -88,6 +193,11 @@ export type InvokeRequest = Message<"quixos.runtime.InvokeRequest"> & {
* @generated from field: repeated quixos.InjectedDependency dependencies = 5;
*/
dependencies: InjectedDependency[];
/**
* @generated from field: quixos.runtime.InvocationContext context = 6;
*/
context?: InvocationContext | undefined;
};
/**
@@ -95,7 +205,7 @@ export type InvokeRequest = Message<"quixos.runtime.InvokeRequest"> & {
* Use `create(InvokeRequestSchema)` to create a new message.
*/
export const InvokeRequestSchema: GenMessage<InvokeRequest> = /*@__PURE__*/
messageDesc(file_quixos_runtime, 2);
messageDesc(file_quixos_runtime, 5);
/**
* @generated from message quixos.runtime.InvokeResponse
@@ -115,6 +225,11 @@ export type InvokeResponse = Message<"quixos.runtime.InvokeResponse"> & {
* @generated from field: string error = 3;
*/
error: string;
/**
* @generated from field: repeated quixos.runtime.DerivedDependency dependencies = 4;
*/
dependencies: DerivedDependency[];
};
/**
@@ -122,7 +237,7 @@ export type InvokeResponse = Message<"quixos.runtime.InvokeResponse"> & {
* Use `create(InvokeResponseSchema)` to create a new message.
*/
export const InvokeResponseSchema: GenMessage<InvokeResponse> = /*@__PURE__*/
messageDesc(file_quixos_runtime, 3);
messageDesc(file_quixos_runtime, 6);
/**
* @generated from message quixos.runtime.WatchRequest
@@ -152,6 +267,11 @@ export type WatchRequest = Message<"quixos.runtime.WatchRequest"> & {
* @generated from field: repeated quixos.InjectedDependency dependencies = 5;
*/
dependencies: InjectedDependency[];
/**
* @generated from field: quixos.runtime.InvocationContext context = 6;
*/
context?: InvocationContext | undefined;
};
/**
@@ -159,7 +279,7 @@ export type WatchRequest = Message<"quixos.runtime.WatchRequest"> & {
* Use `create(WatchRequestSchema)` to create a new message.
*/
export const WatchRequestSchema: GenMessage<WatchRequest> = /*@__PURE__*/
messageDesc(file_quixos_runtime, 4);
messageDesc(file_quixos_runtime, 7);
/**
* @generated from message quixos.runtime.DerivedDependency
@@ -191,7 +311,7 @@ export type DerivedDependency = Message<"quixos.runtime.DerivedDependency"> & {
* Use `create(DerivedDependencySchema)` to create a new message.
*/
export const DerivedDependencySchema: GenMessage<DerivedDependency> = /*@__PURE__*/
messageDesc(file_quixos_runtime, 5);
messageDesc(file_quixos_runtime, 8);
/**
* @generated from message quixos.runtime.WatchEvent
@@ -228,7 +348,7 @@ export type WatchEvent = Message<"quixos.runtime.WatchEvent"> & {
* Use `create(WatchEventSchema)` to create a new message.
*/
export const WatchEventSchema: GenMessage<WatchEvent> = /*@__PURE__*/
messageDesc(file_quixos_runtime, 6);
messageDesc(file_quixos_runtime, 9);
/**
* @generated from service quixos.runtime.PackageRuntime
@@ -258,6 +378,22 @@ export const PackageRuntime: GenService<{
input: typeof WatchRequestSchema;
output: typeof WatchEventSchema;
},
/**
* @generated from rpc quixos.runtime.PackageRuntime.GetInvocationStatus
*/
getInvocationStatus: {
methodKind: "unary";
input: typeof InvocationControlRequestSchema;
output: typeof InvocationStatusSchema;
},
/**
* @generated from rpc quixos.runtime.PackageRuntime.CancelInvocation
*/
cancelInvocation: {
methodKind: "unary";
input: typeof InvocationControlRequestSchema;
output: typeof InvocationStatusSchema;
},
}> = /*@__PURE__*/
serviceDesc(file_quixos_runtime, 0);
+49
View File
@@ -0,0 +1,49 @@
/** Opaque runtime identity. The wire codec, never ordinary package state, owns
* the raw ID. These handles do not themselves confer authority or a lease. */
const identities = new WeakMap<object, string>();
declare const referenceBrand: unique symbol;
export interface QxObjectRef<Identity extends string = string> {
readonly [referenceBrand]: { readonly [K in Identity]: true };
equals(other: QxObjectRef<string>): boolean;
}
class Reference {
constructor(id: string) {
identities.set(this, id);
Object.freeze(this);
}
equals(other: unknown) {
return isObjectReference(other) && identities.get(this) === identities.get(other);
}
toJSON(): never {
throw new Error("Object references cannot be serialized into ordinary data");
}
toString(): never {
throw new Error("Object references cannot be coerced to strings");
}
[Symbol.toPrimitive](): never {
throw new Error("Object references cannot be coerced to scalar values");
}
}
export const isObjectReference = (value: unknown): value is QxObjectRef =>
typeof value === "object" && value !== null && identities.has(value);
/** Internal transport boundary; intentionally not exported from the SDK entry. */
export const referenceFromWire = (id: string): QxObjectRef => {
if (typeof id !== "string" || !id) throw new Error("Missing object reference identity");
return new Reference(id) as unknown as QxObjectRef;
};
export const referenceToWire = (value: unknown): string => {
if (!isObjectReference(value)) throw new Error("Expected an opaque object reference, not a raw ID");
return identities.get(value)!;
};
export const assertReferenceFree = (value: unknown, seen = new Set<object>()): void => {
if (!value || typeof value !== "object") return;
if (isObjectReference(value))
throw new Error(
"Managed references belong in declared RPC references or graph relationships, not ordinary state/messages",
);
if (seen.has(value)) throw new Error("Cyclic ordinary data");
seen.add(value);
if (!(value instanceof Uint8Array)) for (const child of Object.values(value)) assertReferenceFree(child, seen);
seen.delete(value);
};
+76
View File
@@ -0,0 +1,76 @@
import type { QxObjectRef } from "./references.js";
import type { RelationshipCollection, RelationshipEntry } from "./index.js";
type Key = string | boolean | bigint;
type Port<T extends QxObjectRef> = {
collection(): Promise<RelationshipCollection<T>>;
replace(entries: RelationshipEntry<T>[], expectedRevision: bigint): Promise<RelationshipCollection<T>>;
};
const checked = async <T extends QxObjectRef>(port: Port<T>, revision: bigint) => {
const snapshot = await port.collection();
if (snapshot.revision !== revision) throw new Error("STALE_COLLECTION_REVISION");
return snapshot;
};
/** Helpers never retry a failed CAS or silently overwrite concurrent edits. */
export const relationshipMap = <T extends QxObjectRef, K extends Key = Key>(port: Port<T>) => ({
read: () => port.collection(),
async get(key: K) {
const snapshot = await port.collection();
return { revision: snapshot.revision, value: snapshot.entries.find((entry) => entry.key === key)?.target };
},
async set(key: K, target: T, expectedRevision: bigint) {
const snapshot = await checked(port, expectedRevision);
const entries = snapshot.entries.filter((entry) => entry.key !== key);
const existing = snapshot.entries.find((entry) => entry.key === key && entry.target.equals(target));
entries.push(existing ?? { key, target });
return port.replace(entries, expectedRevision);
},
async delete(key: K, expectedRevision: bigint) {
const snapshot = await checked(port, expectedRevision);
return port.replace(
snapshot.entries.filter((entry) => entry.key !== key),
expectedRevision,
);
},
});
export const relationshipList = <T extends QxObjectRef>(port: Port<T>) => ({
read: () => port.collection(),
async insert(index: number, target: T, expectedRevision: bigint) {
const snapshot = await checked(port, expectedRevision);
if (!Number.isSafeInteger(index) || index < 0 || index > snapshot.entries.length)
throw new Error("List index out of bounds");
snapshot.entries.splice(index, 0, { target });
return port.replace(snapshot.entries, expectedRevision);
},
async move(edgeId: string, index: number, expectedRevision: bigint) {
const snapshot = await checked(port, expectedRevision);
const prior = snapshot.entries.findIndex((entry) => entry.edgeId === edgeId);
if (prior < 0 || !Number.isSafeInteger(index) || index < 0 || index >= snapshot.entries.length)
throw new Error("Unknown list entry or invalid index");
const [entry] = snapshot.entries.splice(prior, 1);
snapshot.entries.splice(index, 0, entry);
return port.replace(snapshot.entries, expectedRevision);
},
async delete(edgeId: string, expectedRevision: bigint) {
const snapshot = await checked(port, expectedRevision);
if (!snapshot.entries.some((entry) => entry.edgeId === edgeId)) throw new Error("Unknown list entry");
return port.replace(
snapshot.entries.filter((entry) => entry.edgeId !== edgeId),
expectedRevision,
);
},
});
export const relationshipSet = <T extends QxObjectRef>(port: Port<T>) => ({
read: () => port.collection(),
async add(target: T, expectedRevision: bigint) {
const snapshot = await checked(port, expectedRevision);
if (snapshot.entries.some((entry) => entry.target.equals(target))) return snapshot;
return port.replace([...snapshot.entries, { target }], expectedRevision);
},
async delete(target: T, expectedRevision: bigint) {
const snapshot = await checked(port, expectedRevision);
return port.replace(
snapshot.entries.filter((entry) => !entry.target.equals(target)),
expectedRevision,
);
},
});
+212
View File
@@ -0,0 +1,212 @@
import assert from "node:assert/strict";
import test from "node:test";
import { referenceFromWire } from "../dist/references.js";
import {
bindQxHandler,
decodeQxValue,
encodeQxValue,
jsToProtoValue,
liveValue,
protoValueToJs,
} from "../dist/index.js";
const scalar = (name) => ({ kind: "scalar", name });
const unit = { kind: "builtin", name: "unit" };
test("free function handlers never acquire a receiver or object session", async () => {
const handler = bindQxHandler(
{ receiver: "none", inputType: unit, outputType: unit, ports: {} },
async (context) => {
assert.equal("objectId" in context, false);
assert.equal("openSession" in context, false);
},
{},
);
await handler({
inputProto: {},
get objectId() {
throw new Error("no receiver exists");
},
openSession() {
assert.fail("no object session");
},
});
});
test("React props no longer have an opaque-message reference escape hatch", () => {
const descriptorId = "org.quixos.web-studio.ReactProps";
const reference = referenceFromWire("obj:task");
const type = { kind: "message", descriptorId };
const messages = { [descriptorId]: { encode: jsToProtoValue, decode: protoValueToJs } };
assert.throws(() => encodeQxValue(type, { subject: reference }, messages), /Managed references/);
assert.throws(() => decodeQxValue(type, jsToProtoValue({ subject: reference }), messages), /Managed references/);
});
test("declared record inputs preserve opaque references without opening message codecs", async () => {
const target = referenceFromWire("obj:board");
const type = {
kind: "record",
fields: {
target: { kind: "object-ref", expectation: { kind: "atom", atomId: "board" } },
x: scalar("double"),
note: { kind: "optional", value: scalar("string") },
},
};
const encoded = encodeQxValue(type, { target, x: 12 }, {});
const decoded = decodeQxValue(type, encoded, {});
assert.ok(decoded.target.equals(target));
assert.equal(decoded.x, 12);
assert.equal(decoded.note, null);
assert.throws(() => JSON.stringify(decoded), /cannot be serialized/);
assert.throws(() => encodeQxValue(type, { target, x: 12, hidden: target }, {}), /Unexpected/);
assert.throws(() => encodeQxValue(type, { x: 12 }, {}), /Missing/);
assert.throws(() => encodeQxValue(type, { target: "obj:board", x: 12 }, {}), /opaque object reference/);
const handler = bindQxHandler(
{ inputType: type, outputType: unit, ports: {} },
async (context) => {
assert.ok(context.input.target.equals(target));
assert.equal(context.input.x, 12);
},
{},
);
await handler({ objectId: target, inputProto: encoded.kind.value.fields });
});
test("typed sessions rebind ports and cancellation to each acquired invocation", async () => {
const first = new AbortController(),
second = new AbortController();
let closed = false;
const context = (value, signal) => ({
objectId: referenceFromWire("obj:owner"),
inputProto: {},
signal,
state: () => ({ live: async () => liveValue(jsToProtoValue(value)) }),
});
const raw = {
...context(1n, first.signal),
openSession: async () => ({
id: "session:test",
run: (work) => work(context(2n, second.signal)),
close: async () => {
closed = true;
},
}),
};
const handler = bindQxHandler(
{
inputType: unit,
outputType: unit,
ports: { counter: { kind: "state", id: "counter", valueType: scalar("int64"), primitives: ["read"] } },
},
async (bound) => {
assert.equal(bound.signal, first.signal);
const session = await bound.openSession();
await session.run(async (next) => {
assert.equal(next.signal, second.signal);
assert.equal(await next.ports.counter.get(), 2n);
assert.equal(next.openSession, undefined);
});
await session.close();
},
{},
);
await handler(raw);
assert.equal(closed, true);
});
test("binding codecs round trip nested bytes, 64-bit integers, nulls, and references", () => {
const values = [
[scalar("int64"), -(2n ** 63n)],
[scalar("uint64"), 2n ** 64n - 1n],
[scalar("bytes"), new Uint8Array([0, 255])],
[{ kind: "list", value: { kind: "optional", value: scalar("int64") } }, [null, 2n ** 60n]],
[{ kind: "object-ref", expectation: { kind: "atom", atomId: "thing" } }, referenceFromWire("obj:thing")],
];
for (const [type, value] of values) assert.deepEqual(decodeQxValue(type, encodeQxValue(type, value, {}), {}), value);
});
test("opaque references pass declared RPC boundaries but cannot enter ordinary data", () => {
const reference = referenceFromWire("obj:thing");
const type = { kind: "object-ref", expectation: { kind: "atom", atomId: "thing" } };
const roundtrip = decodeQxValue(type, encodeQxValue(type, reference, {}), {});
assert.equal(reference.equals(roundtrip), true);
assert.equal(reference.equals(referenceFromWire("obj:other")), false);
assert.throws(() => JSON.stringify({ nested: [reference] }), /cannot be serialized/);
assert.throws(() => String(reference), /cannot be coerced/);
assert.throws(() => encodeQxValue(type, "obj:thing", {}), /opaque/);
assert.throws(() => encodeQxValue(scalar("string"), reference, {}), /Managed references/);
const message = { kind: "message", descriptorId: "Payload" };
assert.throws(
() => encodeQxValue(message, { nested: reference }, { Payload: { encode: jsToProtoValue } }),
/Managed references/,
);
assert.throws(
() => encodeQxValue(message, {}, { Payload: { encode: () => jsToProtoValue(reference) } }),
/Managed references/,
);
assert.throws(
() => decodeQxValue(message, jsToProtoValue(reference), { Payload: { decode: () => ({}) } }),
/Managed references/,
);
});
test("typed state and interface ports preserve declared values and exact operation IDs", async () => {
const spec = {
inputType: scalar("int64"),
outputType: scalar("bytes"),
ports: {
data: { kind: "state", id: "state-id", valueType: scalar("int64"), primitives: ["read", "write"] },
reader: {
kind: "interface",
id: "interface-id",
operations: { "payload.get": { id: "get-id", inputType: unit, outputType: scalar("bytes") } },
},
},
};
let written;
const handler = bindQxHandler(
spec,
async ({ input, ports }) => {
assert.equal(input, 2n ** 60n);
assert.equal(await ports.data.get(), 9n);
assert.deepEqual((await ports.data.live()).$quixosValue, jsToProtoValue(9n));
assert.ok(ports.reader.objectId.equals(referenceFromWire("obj:reader")));
assert.deepEqual((await ports.reader.live["payload.get"]()).$quixosValue, jsToProtoValue(new Uint8Array([7])));
await ports.data.set(input);
return ports.reader["payload.get"]();
},
{},
);
const result = await handler({
inputProto: { value: jsToProtoValue(2n ** 60n) },
objectId: "obj",
state: (id) => {
assert.equal(id, "state-id");
return {
live: async () => liveValue(jsToProtoValue(9n)),
set: async (value) => {
written = value;
},
};
},
interface: (id) => {
assert.equal(id, "interface-id");
return {
objectId: referenceFromWire("obj:reader"),
live: async (operation, input) => {
assert.equal(operation, "get-id");
assert.deepEqual(input, {});
return liveValue(jsToProtoValue(new Uint8Array([7])));
},
};
},
});
assert.equal(written.$quixosValue.kind.value, String(2n ** 60n));
assert.deepEqual(result.$quixosValue.kind.value, new Uint8Array([7]));
});
test("external message bindings and derived event types are used at the boundary", async () => {
const message = { kind: "message", descriptorId: "Payload" };
const messages = { Payload: { encode: jsToProtoValue, decode: protoValueToJs } };
const handler = bindQxHandler(
{ inputType: message, outputType: { kind: "builtin", name: "watch-handle" }, eventType: message, ports: {} },
{ kind: "derived", get: ({ input }) => ({ value: input.title }) },
messages,
);
assert.equal(handler.kind, "derived");
const result = await handler.get({ objectId: "obj", inputProto: { title: jsToProtoValue("hello") } });
assert.deepEqual(protoValueToJs(result.$quixosValue), { value: "hello" });
assert.throws(() => encodeQxValue(message, {}, {}), /Missing message binding/);
});
+47
View File
@@ -0,0 +1,47 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import crypto from "node:crypto";
import { spawnSync } from "node:child_process";
test("supervised SDK reads a credential file and proves instance identity without an environment token", async (t) => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-sdk-credential-"));
t.after(() => fs.rm(directory, { recursive: true, force: true }));
const token = crypto.randomBytes(32).toString("hex");
const filename = path.join(directory, "process-token");
await fs.writeFile(filename, token, { mode: 0o600 });
const env = {
...process.env,
CAMINO_RUNTIME_AUTH_TOKEN_FILE: filename,
CAMINO_RUNTIME_AUTH_REQUIRED: "1",
QUIXOS_RUNTIME_INSTANCE_ID: "instance:test",
};
delete env.CAMINO_RUNTIME_AUTH_TOKEN;
const result = spawnSync(
process.execPath,
[
"--input-type=module",
"-e",
`
import {createPackageRuntimeRoutes} from ${JSON.stringify(new URL("../dist/index.js", import.meta.url).href)};
createPackageRuntimeRoutes({packageRevisionId:'package:test',exports:{}})({
service(_type, implementation) { console.log(JSON.stringify(implementation.handshake({nonce:'challenge'}))); }
});
`,
],
{ env, encoding: "utf8" },
);
assert.equal(result.status, 0, result.stderr);
const handshake = JSON.parse(result.stdout);
assert.equal(
handshake.authenticationProof,
crypto
.createHmac("sha256", token)
.update(JSON.stringify(["challenge", "instance:test", "package:test"]))
.digest("hex"),
);
assert.ok(handshake.capabilities.includes("epoch-grants-v1"));
assert.ok(!result.stdout.includes(token));
});
+15
View File
@@ -0,0 +1,15 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createInvocationRegistry } from "../dist/invocations.js";
test("cancel requests do not acknowledge execution completion or allow replay", () => {
const registry = createInvocationRegistry();
const execution = registry.begin("call-1");
assert.equal(registry.cancel("call-1").state, "cancellation-requested");
assert.equal(execution.signal.aborted, true);
assert.equal(registry.status("call-1").state, "cancellation-requested");
execution.finish();
assert.equal(registry.status("call-1").state, "completed");
assert.throws(() => registry.begin("call-1"), /already used/);
assert.equal(registry.status("missing").state, "unknown");
});
+36
View File
@@ -0,0 +1,36 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createMigrationContext, migrationObjectId } from "../dist/migration.js";
test("migration context offers restricted ports and deterministic helper identities", () => {
const input = {
schemaVersion: 1,
executionId: "operation:transition",
exportId: "migrate",
ports: [
{
name: "old",
binding: "slot:name",
view: "old",
access: ["read"],
states: [{ objectId: "obj:one", value: "Alice" }],
},
{ name: "new", binding: "slot:name", view: "new", access: ["write"] },
{ name: "newRead", binding: "slot:name", view: "new", access: ["read"] },
{ name: "helpers", binding: "atom:helper", view: "new", access: ["create"] },
],
};
const { context, result } = createMigrationContext(input);
assert.equal(context.read("old", "obj:one"), "Alice");
assert.throws(() => context.write("old", "obj:one", "Bob"), /does not grant/);
assert.throws(() => context.read("new", "obj:one"), /does not grant/);
context.write("new", "obj:one", "Alice");
assert.equal(context.read("newRead", "obj:one"), "Alice");
context.write("new", "obj:one", "Bob");
assert.equal(context.read("newRead", "obj:one"), "Bob");
assert.equal(context.read("old", "obj:one"), "Alice");
const helper = context.create("helpers", "one");
assert.equal(helper, migrationObjectId(input.executionId, "helpers", "one"));
assert.equal(context.create("helpers", "one"), helper);
assert.equal(result().creates.length, 1);
assert.deepEqual(input.ports[0].states, [{ objectId: "obj:one", value: "Alice" }]);
});
+34
View File
@@ -0,0 +1,34 @@
import test from "node:test";
import assert from "node:assert/strict";
import { relationshipMap, relationshipList, relationshipSet } from "../dist/index.js";
import { referenceFromWire } from "../dist/references.js";
test("relationship helpers preserve handles and require explicit collection revisions", async () => {
let current = { revision: 0n, entries: [] },
serial = 0;
const port = {
collection: async () => ({ revision: current.revision, entries: current.entries.map((entry) => ({ ...entry })) }),
replace: async (entries, revision) => {
assert.equal(revision, current.revision);
current = {
revision: revision + 1n,
entries: entries.map((entry) => ({ ...entry, edgeId: entry.edgeId ?? `edge:${++serial}` })),
};
return port.collection();
},
};
const a = referenceFromWire("obj:a"),
b = referenceFromWire("obj:b");
const map = relationshipMap(port);
await map.set("a1", a, 0n);
assert.equal((await map.get("a1")).value.equals(a), true);
await assert.rejects(() => map.set("b1", b, 0n), /STALE_COLLECTION/);
await map.delete("a1", 1n);
const list = relationshipList(port);
const inserted = await list.insert(0, a, 2n);
await list.insert(1, b, 3n);
const moved = await list.move(inserted.entries[0].edgeId, 1, 4n);
assert.equal(moved.entries[1].target.equals(a), true);
const set = relationshipSet(port);
const unchanged = await set.add(referenceFromWire("obj:a"), 5n);
assert.equal(unchanged.revision, 5n);
});
+334 -77
View File
@@ -1,6 +1,7 @@
import assert from "node:assert/strict";
import http from "node:http";
import test from "node:test";
import { referenceFromWire } from "../dist/references.js";
import { create } from "@bufbuild/protobuf";
import { createClient } from "@connectrpc/connect";
import { connectNodeAdapter, createConnectTransport } from "@connectrpc/connect-node";
@@ -9,18 +10,20 @@ import {
createRuntimeContext,
derived,
jsToProtoValue,
liveValue,
protoValueToJs,
} from "../dist/index.js";
import {
CaminoObjectSchema,
CaminoService,
CrdtValueSchema,
StateValueSourceSchema,
ValueSchema,
ValueSourceSchema,
} from "../dist/camino/api_pb.js";
import {
EdgeDependencySchema,
InjectedDependencySchema,
PackageExportRefSchema,
} from "../dist/quixos/refs_pb.js";
import { PackageRuntime } from "../dist/quixos/runtime_pb.js";
import { EdgeDependencySchema, InjectedDependencySchema, PackageExportRefSchema } from "../dist/quixos/refs_pb.js";
import { InvokeCapabilityResponseSchema, OrchestratorRuntime } from "../dist/quixos/orch_pb.js";
import { DerivedDependencySchema, PackageRuntime } from "../dist/quixos/runtime_pb.js";
const listen = async (routes) => {
const server = http.createServer((request, response) => void connectNodeAdapter({ routes })(request, response));
@@ -39,16 +42,40 @@ const listen = async (routes) => {
test("generic values preserve nested values and object references", () => {
const value = jsToProtoValue({
title: "A task",
target: { $quixosRef: "obj:target" },
target: referenceFromWire("obj:target"),
tags: ["one", "two"],
});
assert.deepEqual(protoValueToJs(value), {
title: "A task",
target: "obj:target",
target: referenceFromWire("obj:target"),
tags: ["one", "two"],
});
});
test("live values preserve writable CRDT source identity through derivation", () => {
const value = create(ValueSchema, {
kind: { case: "stringValue", value: "notes" },
source: create(ValueSourceSchema, {
state: create(StateValueSourceSchema, {
objectId: "obj:task",
slotId: "slot:task:notes",
revision: 7n,
crdtSnapshot: create(CrdtValueSchema, {
type: "quixos.automerge-document.v1",
encoding: "automerge-snapshot-v1",
payload: new Uint8Array([1, 2, 3]),
}),
}),
}),
});
const forwarded = jsToProtoValue(liveValue(value));
assert.equal(forwarded.source?.state?.objectId, "obj:task");
assert.equal(forwarded.source?.state?.slotId, "slot:task:notes");
assert.equal(forwarded.source?.state?.revision, 7n);
assert.deepEqual(forwarded.source?.state?.crdtSnapshot?.payload, new Uint8Array([1, 2, 3]));
});
test("runtime context exposes only explicitly injected ports", async () => {
const writes = [];
const camino = {
@@ -62,26 +89,30 @@ test("runtime context exposes only explicitly injected ports", async () => {
resolveEdge: async () => ({ edges: [] }),
connectEdge: async () => ({}),
};
const context = createRuntimeContext(camino, {}, {
objectId: "obj:task",
input: {},
dependencies: [
create(InjectedDependencySchema, {
portId: "port:title",
binding: { case: "stateSlotId", value: "slot:task:title" },
}),
create(InjectedDependencySchema, {
portId: "port:children",
binding: {
case: "edge",
value: create(EdgeDependencySchema, {
edgeTypeId: "edge:task:children",
projectionId: "projection:task:children",
}),
},
}),
],
});
const context = createRuntimeContext(
camino,
{},
{
objectId: "obj:task",
input: {},
dependencies: [
create(InjectedDependencySchema, {
portId: "port:title",
binding: { case: "stateSlotId", value: "slot:task:title" },
}),
create(InjectedDependencySchema, {
portId: "port:children",
binding: {
case: "edge",
value: create(EdgeDependencySchema, {
edgeTypeId: "edge:task:children",
projectionId: "projection:task:children",
}),
},
}),
],
},
);
assert.equal(await context.state("port:title").get(), "obj:task/slot:task:title");
await context.state("port:title").set("Changed");
@@ -93,60 +124,286 @@ test("runtime context exposes only explicitly injected ports", async () => {
assert.deepEqual(await context.edge("port:children").resolve(), []);
});
test("derived watches subscribe before reading and emit only real changes", async () => {
let current = "before";
let subscribed = false;
const changes = [];
const caminoServer = await listen((router) => router.service(CaminoService, {
readState: () => {
assert.equal(subscribed, true, "the dependency stream must be live before the state read");
return { value: jsToProtoValue(current) };
},
watchObject: async function* (request, context) {
subscribed = true;
yield {
objectId: request.objectId,
snapshot: create(CaminoObjectSchema, {
id: request.objectId,
atomId: "atom:test",
workspaceRevisionId: "workspace:test@1",
}),
};
while (!context.signal.aborted) {
const changed = await new Promise((resolve) => {
const finish = () => resolve(true);
changes.push(finish);
context.signal.addEventListener("abort", () => resolve(false), { once: true });
});
if (!changed) return;
yield { objectId: request.objectId };
}
},
}));
const runtimeServer = await listen(createPackageRuntimeRoutes({
packageRevisionId: "package:test@1",
caminoUrl: caminoServer.url,
exports: {
"export:test:value": derived((context) => context.state("port:value").get()),
},
}));
const client = createClient(PackageRuntime, createConnectTransport({
baseUrl: runtimeServer.url,
httpVersion: "1.1",
}));
const controller = new AbortController();
test("injected ports preserve an explicitly traversed object through PackageRuntime RPC", async () => {
const reads = [];
const caminoServer = await listen((router) =>
router.service(CaminoService, {
readState: (request) => {
reads.push(request);
return { value: jsToProtoValue("Project name") };
},
}),
);
const runtimeServer = await listen(
createPackageRuntimeRoutes({
packageRevisionId: "package:test@1",
caminoUrl: caminoServer.url,
exports: {
"export:test:value": (context) => context.state("port:value").get(),
},
}),
);
const client = createClient(
PackageRuntime,
createConnectTransport({
baseUrl: runtimeServer.url,
httpVersion: "1.1",
}),
);
try {
const stream = client.watch({
const response = await client.invoke({
export: create(PackageExportRefSchema, {
packageRevisionId: "package:test@1",
exportId: "export:test:value",
}),
objectId: "obj:test",
dependencies: [create(InjectedDependencySchema, {
portId: "port:value",
binding: { case: "stateSlotId", value: "slot:test:value" },
})],
}, { signal: controller.signal })[Symbol.asyncIterator]();
objectId: "obj:component",
dependencies: [
create(InjectedDependencySchema, {
portId: "port:value",
objectId: "obj:project",
binding: { case: "stateSlotId", value: "slot:project:name" },
}),
],
});
assert.equal(response.ok, true);
assert.equal(protoValueToJs(response.result), "Project name");
assert.equal(reads.length, 1);
assert.equal(reads[0]?.objectId, "obj:project");
} finally {
await runtimeServer.close();
await caminoServer.close();
}
});
test("interface views invoke a related object and propagate transitive live dependencies", async () => {
const invocations = [];
const sourceValue = create(ValueSchema, {
kind: { case: "stringValue", value: "Project name" },
source: create(ValueSourceSchema, {
state: create(StateValueSourceSchema, {
objectId: "obj:project",
slotId: "slot:project:name",
revision: 4n,
}),
}),
});
const orchServer = await listen((router) =>
router.service(OrchestratorRuntime, {
invokeCapability: (request) => {
invocations.push(request);
return create(InvokeCapabilityResponseSchema, {
ok: true,
result: sourceValue,
dependencies: [
create(DerivedDependencySchema, {
kind: "state",
objectId: "obj:project",
attachmentId: "slot:project:name",
}),
],
});
},
}),
);
const runtimeServer = await listen(
createPackageRuntimeRoutes({
packageRevisionId: "package:test@1",
orchUrl: orchServer.url,
exports: {
"export:test:value": derived((context) => context.interface("port:named").live("operation:named:name:get")),
},
}),
);
const client = createClient(
PackageRuntime,
createConnectTransport({
baseUrl: runtimeServer.url,
httpVersion: "1.1",
}),
);
try {
const response = await client.invoke({
export: create(PackageExportRefSchema, {
packageRevisionId: "package:test@1",
exportId: "export:test:value",
}),
objectId: "obj:component",
dependencies: [
create(InjectedDependencySchema, {
portId: "port:named",
objectId: "obj:project",
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
}),
],
});
assert.equal(response.ok, true);
assert.equal(invocations[0]?.objectId, "obj:project");
assert.equal(invocations[0]?.capability?.interfaceRevisionId, "interface:named@1");
assert.equal(response.dependencies[0]?.objectId, "obj:project");
assert.equal(response.dependencies[0]?.attachmentId, "slot:project:name");
assert.equal(response.result?.source?.state?.revision, 4n);
} finally {
await runtimeServer.close();
await orchServer.close();
}
});
test("derived interface views reread after their transitive subscriptions become live", async () => {
let current = "before subscription";
let invocationCount = 0;
const caminoServer = await listen((router) =>
router.service(CaminoService, {
watchObject: async function* (request, context) {
// Model a write racing the first nested capability read. Camino makes
// the subscription live before yielding this snapshot.
current = "after subscription";
yield {
objectId: request.objectId,
snapshot: create(CaminoObjectSchema, {
id: request.objectId,
atomId: "atom:project",
workspaceRevisionId: "workspace:test@1",
}),
};
await new Promise((resolve) => context.signal.addEventListener("abort", resolve, { once: true }));
},
}),
);
const orchServer = await listen((router) =>
router.service(OrchestratorRuntime, {
invokeCapability: () => {
invocationCount += 1;
return create(InvokeCapabilityResponseSchema, {
ok: true,
result: jsToProtoValue(current),
dependencies: [
create(DerivedDependencySchema, {
kind: "state",
objectId: "obj:project",
attachmentId: "slot:project:name",
}),
],
});
},
}),
);
const runtimeServer = await listen(
createPackageRuntimeRoutes({
packageRevisionId: "package:test@1",
caminoUrl: caminoServer.url,
orchUrl: orchServer.url,
exports: {
"export:test:value": derived((context) => context.interface("port:named").invoke("operation:named:name:get")),
},
}),
);
const client = createClient(
PackageRuntime,
createConnectTransport({
baseUrl: runtimeServer.url,
httpVersion: "1.1",
}),
);
const controller = new AbortController();
try {
const stream = client
.watch(
{
export: create(PackageExportRefSchema, {
packageRevisionId: "package:test@1",
exportId: "export:test:value",
}),
objectId: "obj:component",
dependencies: [
create(InjectedDependencySchema, {
portId: "port:named",
objectId: "obj:project",
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
}),
],
},
{ signal: controller.signal },
)
[Symbol.asyncIterator]();
const initial = await stream.next();
assert.equal(protoValueToJs(initial.value?.value), "after subscription");
assert.equal(invocationCount, 2);
} finally {
controller.abort();
await runtimeServer.close();
await orchServer.close();
await caminoServer.close();
}
});
test("derived watches subscribe before reading and emit only real changes", async () => {
let current = "before";
let subscribed = false;
const changes = [];
const caminoServer = await listen((router) =>
router.service(CaminoService, {
readState: () => {
assert.equal(subscribed, true, "the dependency stream must be live before the state read");
return { value: jsToProtoValue(current) };
},
watchObject: async function* (request, context) {
subscribed = true;
yield {
objectId: request.objectId,
snapshot: create(CaminoObjectSchema, {
id: request.objectId,
atomId: "atom:test",
workspaceRevisionId: "workspace:test@1",
}),
};
while (!context.signal.aborted) {
const changed = await new Promise((resolve) => {
const finish = () => resolve(true);
changes.push(finish);
context.signal.addEventListener("abort", () => resolve(false), { once: true });
});
if (!changed) return;
yield { objectId: request.objectId };
}
},
}),
);
const runtimeServer = await listen(
createPackageRuntimeRoutes({
packageRevisionId: "package:test@1",
caminoUrl: caminoServer.url,
exports: {
"export:test:value": derived((context) => context.state("port:value").get()),
},
}),
);
const client = createClient(
PackageRuntime,
createConnectTransport({
baseUrl: runtimeServer.url,
httpVersion: "1.1",
}),
);
const controller = new AbortController();
try {
const stream = client
.watch(
{
export: create(PackageExportRefSchema, {
packageRevisionId: "package:test@1",
exportId: "export:test:value",
}),
objectId: "obj:test",
dependencies: [
create(InjectedDependencySchema, {
portId: "port:value",
binding: { case: "stateSlotId", value: "slot:test:value" },
}),
],
},
{ signal: controller.signal },
)
[Symbol.asyncIterator]();
const initial = await stream.next();
assert.equal(protoValueToJs(initial.value?.value), "before");
assert.equal(initial.value?.initial, true);