Carry editable field authority in CultMesh bindings

This commit is contained in:
Meta Vens 2026-08-22 00:05:57 +02:00
commit 4f6e95bb3d
5 changed files with 257 additions and 8 deletions

View file

@ -300,6 +300,13 @@ export interface CultMeshStateBindingDescriptor {
readonly sourceId?: string;
readonly schemaId?: string;
readonly routeHint: CultMeshRouteHint;
readonly bindingName?: string;
readonly documentId?: string;
readonly fieldPath?: string;
readonly valueKind?: "string" | "number" | "boolean" | "choice" | "string-list";
readonly accessMode?: "read" | "write" | "read-write" | "local-draft";
readonly authority?: string;
readonly writeCommand?: string;
}
export interface CultMeshStateBindingRecord {
@ -309,6 +316,13 @@ export interface CultMeshStateBindingRecord {
readonly schemaId: string;
readonly routeKind: string;
readonly routeDescription: string;
readonly bindingName: string;
readonly documentId: string;
readonly fieldPath: string;
readonly valueKind: string;
readonly accessMode: string;
readonly authority: string;
readonly writeCommand: string;
}
export interface CultMeshOperationBindingDescriptor {
@ -3351,6 +3365,15 @@ export function cultMeshDescribeStateRefResolver(
};
}
function stateBindingMetadata(
options: Pick<CultMeshStateBindingDescriptor,
"bindingName" | "documentId" | "fieldPath" | "valueKind" | "accessMode" | "authority" | "writeCommand">,
): Partial<CultMeshStateBindingDescriptor> {
return Object.fromEntries(
Object.entries(options).filter(([, value]) => value !== undefined),
) as Partial<CultMeshStateBindingDescriptor>;
}
export function cultMeshStateBinding(
targetProp: string,
pointer:
@ -3364,6 +3387,13 @@ export function cultMeshStateBinding(
sourceId?: string;
schemaId?: string;
routeHint?: CultMeshRouteHint;
bindingName?: string;
documentId?: string;
fieldPath?: string;
valueKind?: CultMeshStateBindingDescriptor["valueKind"];
accessMode?: CultMeshStateBindingDescriptor["accessMode"];
authority?: string;
writeCommand?: string;
} = {},
): CultMeshStateBindingDescriptor {
requireNonEmpty(targetProp, "targetProp");
@ -3375,6 +3405,7 @@ export function cultMeshStateBinding(
sourceId: options.sourceId,
schemaId: options.schemaId,
routeHint: options.routeHint ?? cultMeshRouteHint(),
...stateBindingMetadata(options),
};
}
@ -3386,6 +3417,7 @@ export function cultMeshStateBinding(
sourceId: options.sourceId ?? source?.sourceId,
schemaId: options.schemaId ?? source?.schemaId,
routeHint: options.routeHint ?? pointer.routeHint ?? cultMeshRouteHint(),
...stateBindingMetadata(options),
};
}
@ -3399,6 +3431,13 @@ export function cultMeshStateBindingRecord(
schemaId?: string,
routeKind?: string,
routeDescription?: string,
bindingName?: string,
documentId?: string,
fieldPath?: string,
valueKind?: string,
accessMode?: string,
authority?: string,
writeCommand?: string,
): CultMeshStateBindingRecord;
export function cultMeshStateBindingRecord(
bindingOrTargetProp?: Partial<CultMeshStateBindingDescriptor> | string,
@ -3407,6 +3446,13 @@ export function cultMeshStateBindingRecord(
schemaId?: string,
routeKind?: string,
routeDescription?: string,
bindingName?: string,
documentId?: string,
fieldPath?: string,
valueKind?: string,
accessMode?: string,
authority?: string,
writeCommand?: string,
): CultMeshStateBindingRecord {
if (typeof bindingOrTargetProp === "object" || bindingOrTargetProp === undefined) {
const binding = bindingOrTargetProp;
@ -3418,6 +3464,13 @@ export function cultMeshStateBindingRecord(
schemaId: binding?.schemaId ?? "",
routeKind: route.kind,
routeDescription: route.description,
bindingName: binding?.bindingName ?? "",
documentId: binding?.documentId ?? "",
fieldPath: binding?.fieldPath ?? "",
valueKind: binding?.valueKind ?? "",
accessMode: binding?.accessMode ?? "",
authority: binding?.authority ?? "",
writeCommand: binding?.writeCommand ?? "",
};
}
@ -3428,6 +3481,13 @@ export function cultMeshStateBindingRecord(
schemaId: schemaId ?? "",
routeKind: routeKind ?? "",
routeDescription: routeDescription ?? "",
bindingName: bindingName ?? "",
documentId: documentId ?? "",
fieldPath: fieldPath ?? "",
valueKind: valueKind ?? "",
accessMode: accessMode ?? "",
authority: authority ?? "",
writeCommand: writeCommand ?? "",
};
}
@ -3451,6 +3511,15 @@ export function cultMeshStateBindingFromRecord(
},
options.fallbackRouteHint,
),
...stateBindingMetadata({
bindingName: record.bindingName || undefined,
documentId: record.documentId || undefined,
fieldPath: record.fieldPath || undefined,
valueKind: (record.valueKind || undefined) as CultMeshStateBindingDescriptor["valueKind"],
accessMode: (record.accessMode || undefined) as CultMeshStateBindingDescriptor["accessMode"],
authority: record.authority || undefined,
writeCommand: record.writeCommand || undefined,
}),
};
}
@ -5278,6 +5347,13 @@ export class CultMesh {
sourceId?: string;
schemaId?: string;
routeHint?: CultMeshRouteHint;
bindingName?: string;
documentId?: string;
fieldPath?: string;
valueKind?: CultMeshStateBindingDescriptor["valueKind"];
accessMode?: CultMeshStateBindingDescriptor["accessMode"];
authority?: string;
writeCommand?: string;
} = {},
): CultMeshStateBindingDescriptor {
return cultMeshStateBinding(targetProp, pointer, options);
@ -5293,6 +5369,13 @@ export class CultMesh {
schemaId?: string,
routeKind?: string,
routeDescription?: string,
bindingName?: string,
documentId?: string,
fieldPath?: string,
valueKind?: string,
accessMode?: string,
authority?: string,
writeCommand?: string,
): CultMeshStateBindingRecord;
public static stateBindingRecord(
bindingOrTargetProp?: Partial<CultMeshStateBindingDescriptor> | string,
@ -5301,6 +5384,13 @@ export class CultMesh {
schemaId?: string,
routeKind?: string,
routeDescription?: string,
bindingName?: string,
documentId?: string,
fieldPath?: string,
valueKind?: string,
accessMode?: string,
authority?: string,
writeCommand?: string,
): CultMeshStateBindingRecord {
return typeof bindingOrTargetProp === "object" || bindingOrTargetProp === undefined
? cultMeshStateBindingRecord(bindingOrTargetProp)
@ -5311,6 +5401,13 @@ export class CultMesh {
schemaId,
routeKind,
routeDescription,
bindingName,
documentId,
fieldPath,
valueKind,
accessMode,
authority,
writeCommand,
);
}

View file

@ -2147,6 +2147,13 @@ test("CultMesh TS flattens and rehydrates UI state binding records", () => {
schemaId: "gamecult.aetheria.daemon_frame.v1",
routeKind: "shared-memory",
routeDescription: "co-located frame slab",
bindingName: "",
documentId: "",
fieldPath: "",
valueKind: "",
accessMode: "",
authority: "",
writeCommand: "",
});
assert.deepEqual(CultMesh.stateBindingFromRecord({
@ -2168,6 +2175,26 @@ test("CultMesh TS flattens and rehydrates UI state binding records", () => {
});
});
test("CultMesh TS preserves editable binding identity and authority through transport records", () => {
const binding = CultMesh.stateBinding("value", "ghostlight.session-zero.composer", {
sourceId: "session-zero:draft-7",
schemaId: "session_zero.v1",
routeHint: CultMesh.routeHint("network", "Ghostlight provider"),
bindingName: "composer.message",
documentId: "session-zero:draft-7",
fieldPath: "channels.private.messages.pending",
valueKind: "string",
accessMode: "local-draft",
authority: "SessionZeroKernel",
writeCommand: "edit.value",
});
const record = CultMesh.stateBindingRecord(binding);
assert.equal(record.bindingName, "composer.message");
assert.equal(record.fieldPath, "channels.private.messages.pending");
assert.equal(record.accessMode, "local-draft");
assert.deepEqual(CultMesh.stateBindingFromRecord(record), binding);
});
test("CultMesh TS describes UI command bindings from typed operations", async () => {
const operation = CultMesh.operation<
{ actor: string },

View file

@ -1117,9 +1117,18 @@ namespace GameCult.Mesh
string pointerId,
string? sourceId = null,
string? schemaId = null,
CultMeshRouteHint? routeHint = null)
CultMeshRouteHint? routeHint = null,
string? bindingName = null,
string? documentId = null,
string? fieldPath = null,
string? valueKind = null,
string? accessMode = null,
string? authority = null,
string? writeCommand = null)
{
return new CultMeshStateBindingDescriptor(targetProp, pointerId, sourceId, schemaId, routeHint);
return new CultMeshStateBindingDescriptor(
targetProp, pointerId, sourceId, schemaId, routeHint,
bindingName, documentId, fieldPath, valueKind, accessMode, authority, writeCommand);
}
/// <summary>
@ -1139,7 +1148,14 @@ namespace GameCult.Mesh
string? sourceId = null,
string? schemaId = null,
string? routeKind = null,
string? routeDescription = null)
string? routeDescription = null,
string? bindingName = null,
string? documentId = null,
string? fieldPath = null,
string? valueKind = null,
string? accessMode = null,
string? authority = null,
string? writeCommand = null)
{
return new CultMeshStateBindingRecord(
targetProp,
@ -1147,7 +1163,14 @@ namespace GameCult.Mesh
sourceId,
schemaId,
routeKind,
routeDescription);
routeDescription,
bindingName,
documentId,
fieldPath,
valueKind,
accessMode,
authority,
writeCommand);
}
/// <summary>

View file

@ -3329,13 +3329,27 @@ namespace GameCult.Mesh
string pointerId,
string? sourceId = null,
string? schemaId = null,
CultMeshRouteHint? routeHint = null)
CultMeshRouteHint? routeHint = null,
string? bindingName = null,
string? documentId = null,
string? fieldPath = null,
string? valueKind = null,
string? accessMode = null,
string? authority = null,
string? writeCommand = null)
{
TargetProp = RequireNonEmpty(targetProp, nameof(targetProp));
PointerId = RequireNonEmpty(pointerId, nameof(pointerId));
SourceId = sourceId;
SchemaId = schemaId;
RouteHint = routeHint ?? CultMeshRouteHint.Automatic;
BindingName = bindingName ?? "";
DocumentId = documentId ?? "";
FieldPath = fieldPath ?? "";
ValueKind = valueKind ?? "";
AccessMode = accessMode ?? "";
Authority = authority ?? "";
WriteCommand = writeCommand ?? "";
}
/// <summary>Gets the component property receiving the resolved state value.</summary>
@ -3353,6 +3367,27 @@ namespace GameCult.Mesh
/// <summary>Gets the preferred route for resolving and watching the binding.</summary>
public CultMeshRouteHint RouteHint { get; }
/// <summary>Gets the stable renderer-facing name used to capture this binding in an operation.</summary>
public string BindingName { get; }
/// <summary>Gets the optional identity of the provider-owned backing document.</summary>
public string DocumentId { get; }
/// <summary>Gets the optional typed field path inside the backing document.</summary>
public string FieldPath { get; }
/// <summary>Gets the optional portable value kind.</summary>
public string ValueKind { get; }
/// <summary>Gets the optional read, write, read-write, or local-draft access mode.</summary>
public string AccessMode { get; }
/// <summary>Gets the authority responsible for accepting writes to this binding.</summary>
public string Authority { get; }
/// <summary>Gets the optional typed operation id used for direct writes.</summary>
public string WriteCommand { get; }
/// <summary>Creates a binding from an existing state pointer.</summary>
public static CultMeshStateBindingDescriptor FromPointer<TValue>(
string targetProp,
@ -3390,7 +3425,14 @@ namespace GameCult.Mesh
string? sourceId = null,
string? schemaId = null,
string? routeKind = null,
string? routeDescription = null)
string? routeDescription = null,
string? bindingName = null,
string? documentId = null,
string? fieldPath = null,
string? valueKind = null,
string? accessMode = null,
string? authority = null,
string? writeCommand = null)
{
TargetProp = string.IsNullOrWhiteSpace(targetProp) ? "value" : targetProp!;
PointerId = pointerId ?? "";
@ -3398,6 +3440,13 @@ namespace GameCult.Mesh
SchemaId = schemaId ?? "";
RouteKind = routeKind ?? "";
RouteDescription = routeDescription ?? "";
BindingName = bindingName ?? "";
DocumentId = documentId ?? "";
FieldPath = fieldPath ?? "";
ValueKind = valueKind ?? "";
AccessMode = accessMode ?? "";
Authority = authority ?? "";
WriteCommand = writeCommand ?? "";
}
/// <summary>Gets the component property receiving the resolved state value.</summary>
@ -3418,6 +3467,14 @@ namespace GameCult.Mesh
/// <summary>Gets the flattened route description.</summary>
[Key(5)] public string RouteDescription { get; }
[Key(6)] public string BindingName { get; }
[Key(7)] public string DocumentId { get; }
[Key(8)] public string FieldPath { get; }
[Key(9)] public string ValueKind { get; }
[Key(10)] public string AccessMode { get; }
[Key(11)] public string Authority { get; }
[Key(12)] public string WriteCommand { get; }
/// <summary>Creates transport-friendly fields from a state binding descriptor.</summary>
public static CultMeshStateBindingRecord FromBinding(CultMeshStateBindingDescriptor? binding)
{
@ -3428,7 +3485,14 @@ namespace GameCult.Mesh
binding?.SourceId,
binding?.SchemaId,
route.Kind,
route.Description);
route.Description,
binding?.BindingName,
binding?.DocumentId,
binding?.FieldPath,
binding?.ValueKind,
binding?.AccessMode,
binding?.Authority,
binding?.WriteCommand);
}
/// <summary>Rehydrates this record as a state binding descriptor.</summary>
@ -3450,7 +3514,14 @@ namespace GameCult.Mesh
pointerId,
SourceId,
SchemaId,
route);
route,
BindingName,
DocumentId,
FieldPath,
ValueKind,
AccessMode,
Authority,
WriteCommand);
}
}

View file

@ -298,6 +298,37 @@ public sealed class CultMeshStreamingTests
fromTypescript.RouteHint.Description.Should().Be("browser-adjacent cache");
}
[Test]
public void StateBindingRecord_PreservesEditableFieldIdentityAndAuthority()
{
var binding = CultMesh.StateBinding(
"value",
"ghostlight.session-zero.composer",
sourceId: "session-zero:draft-7",
schemaId: "session_zero.v1",
routeHint: new CultMeshRouteHint(CultMeshLocalityKind.Network, "Ghostlight provider"),
bindingName: "composer.message",
documentId: "session-zero:draft-7",
fieldPath: "channels.private.messages.pending",
valueKind: "string",
accessMode: "local-draft",
authority: "SessionZeroKernel",
writeCommand: "edit.value");
var record = CultMesh.StateBindingRecord(binding);
record.BindingName.Should().Be("composer.message");
record.FieldPath.Should().Be("channels.private.messages.pending");
record.AccessMode.Should().Be("local-draft");
var restored = record.ToBinding();
restored.BindingName.Should().Be(binding.BindingName);
restored.DocumentId.Should().Be(binding.DocumentId);
restored.FieldPath.Should().Be(binding.FieldPath);
restored.ValueKind.Should().Be(binding.ValueKind);
restored.AccessMode.Should().Be(binding.AccessMode);
restored.Authority.Should().Be(binding.Authority);
restored.WriteCommand.Should().Be(binding.WriteCommand);
}
[Test]
public void OperationBindingDescriptor_BindsUiCommandsToTypedOperations()
{