diff --git a/src/GameCult.Networking/Contracts/cultnet.document-mutation-contract.schema.json b/src/GameCult.Networking/Contracts/cultnet.document-mutation-contract.schema.json
new file mode 100644
index 00000000..e444fef0
--- /dev/null
+++ b/src/GameCult.Networking/Contracts/cultnet.document-mutation-contract.schema.json
@@ -0,0 +1,40 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://github.com/GameCult/cultnet-ts/contracts/cultnet.document-mutation-contract.schema.json",
+ "title": "CultNet Document Mutation Contract",
+ "type": "object",
+ "required": [
+ "documentType",
+ "operations",
+ "authority"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "documentType": { "type": "string", "minLength": 1 },
+ "payloadSchemaVersion": { "type": "string", "minLength": 1 },
+ "operations": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "enum": ["snapshot", "documentPut", "documentDelete", "intentSubmit", "receiptWatch"]
+ }
+ },
+ "authority": {
+ "type": "string",
+ "enum": ["readOnly", "localUser", "coordinator", "runtime", "denied"]
+ },
+ "intentDocumentTypes": {
+ "type": "array",
+ "items": { "type": "string", "minLength": 1 }
+ },
+ "receiptDocumentTypes": {
+ "type": "array",
+ "items": { "type": "string", "minLength": 1 }
+ },
+ "notes": {
+ "type": "array",
+ "items": { "type": "string", "minLength": 1 }
+ }
+ }
+}
diff --git a/src/GameCult.Networking/Contracts/cultnet.hello.schema.json b/src/GameCult.Networking/Contracts/cultnet.hello.schema.json
index f15f7452..bced7577 100644
--- a/src/GameCult.Networking/Contracts/cultnet.hello.schema.json
+++ b/src/GameCult.Networking/Contracts/cultnet.hello.schema.json
@@ -20,6 +20,12 @@
"type": "array",
"items": { "type": "string", "minLength": 1 }
},
+ "supportedMutationContracts": {
+ "type": "array",
+ "items": {
+ "$ref": "./cultnet.document-mutation-contract.schema.json"
+ }
+ },
"supportedMessageVersions": {
"type": "array",
"items": { "type": "string", "minLength": 1 }
diff --git a/src/GameCult.Networking/CultNetSchemaMessages.cs b/src/GameCult.Networking/CultNetSchemaMessages.cs
index 69ff8694..5281fb79 100644
--- a/src/GameCult.Networking/CultNetSchemaMessages.cs
+++ b/src/GameCult.Networking/CultNetSchemaMessages.cs
@@ -81,6 +81,69 @@ namespace GameCult.Networking
public const string SchemaCatalogResponse = "cultnet.schema_catalog_response.v0";
}
+ ///
+ /// Mutation authority owner identifiers advertised through CultNet mutation contracts.
+ ///
+ public static class CultNetMutationAuthorities
+ {
+ ///
+ /// The document is inspectable but not writable through CultNet.
+ ///
+ public const string ReadOnly = "readOnly";
+ ///
+ /// The local human operator may submit the corresponding mutation intent.
+ ///
+ public const string LocalUser = "localUser";
+ ///
+ /// A coordinator-owned Epiphany control surface owns the mutation path.
+ ///
+ public const string Coordinator = "coordinator";
+ ///
+ /// The runtime itself may write this document directly.
+ ///
+ public const string Runtime = "runtime";
+ ///
+ /// The document should not be mutated through CultNet.
+ ///
+ public const string Denied = "denied";
+ }
+
+ ///
+ /// Declares how a document or surface may be inspected and mutated over CultNet.
+ ///
+ [MessagePackObject]
+ public class CultNetDocumentMutationContract
+ {
+ ///
+ /// Gets or sets the stable document type governed by this contract.
+ ///
+ [Key("documentType")] public string DocumentType { get; set; } = string.Empty;
+ ///
+ /// Gets or sets the payload schema version when the contract governs a versioned document payload.
+ ///
+ [Key("payloadSchemaVersion")] public string? PayloadSchemaVersion { get; set; }
+ ///
+ /// Gets or sets the CultNet operations this document accepts.
+ ///
+ [Key("operations")] public string[] Operations { get; set; } = Array.Empty();
+ ///
+ /// Gets or sets which actor is allowed to drive those operations.
+ ///
+ [Key("authority")] public string Authority { get; set; } = CultNetMutationAuthorities.ReadOnly;
+ ///
+ /// Gets or sets the intent document types a client may submit to request mutation.
+ ///
+ [Key("intentDocumentTypes")] public string[]? IntentDocumentTypes { get; set; }
+ ///
+ /// Gets or sets the receipt document types emitted after a successful mutation path.
+ ///
+ [Key("receiptDocumentTypes")] public string[]? ReceiptDocumentTypes { get; set; }
+ ///
+ /// Gets or sets freeform notes that explain local mutation guardrails or posture.
+ ///
+ [Key("notes")] public string[]? Notes { get; set; }
+ }
+
///
/// Marker interface for the explicit modern CultNet schema-v0 message family.
///
@@ -127,6 +190,10 @@ namespace GameCult.Networking
///
[Key("supportedDocumentTypes")] public string[]? SupportedDocumentTypes { get; set; }
///
+ /// Gets or sets the supported mutation contracts.
+ ///
+ [Key("supportedMutationContracts")] public CultNetDocumentMutationContract[]? SupportedMutationContracts { get; set; }
+ ///
/// Gets or sets the supported message versions.
///
[Key("supportedMessageVersions")] public string[]? SupportedMessageVersions { get; set; }
diff --git a/src/GameCult.Networking/CultNetSchemaRegistry.cs b/src/GameCult.Networking/CultNetSchemaRegistry.cs
index 90c2a273..025afa3f 100644
--- a/src/GameCult.Networking/CultNetSchemaRegistry.cs
+++ b/src/GameCult.Networking/CultNetSchemaRegistry.cs
@@ -260,6 +260,12 @@ namespace GameCult.Networking
WireContracts = new[] { CultNetWireContracts.SchemaV0 }
},
new SchemaResourceSpec
+ {
+ FileName = "cultnet.document-mutation-contract.schema.json",
+ Kind = "shared_contract",
+ WireContracts = new[] { CultNetWireContracts.SchemaV0 }
+ },
+ new SchemaResourceSpec
{
FileName = "cultnet.login.schema.json",
Kind = "wire_message",