syntax = "proto3";

package nexuscrm.runtime.v1;

import "common/v1/common.proto";

option go_package = "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1;runtimev1";

// RecordMutationsService owns one coarse root-record command. Identity,
// authority, tenant, and AppInstallation are authenticated transport context;
// they are never caller-selected business fields.
service RecordMutationsService {
  // The inner MutationCommand is protobuf-encoded and split into bounded
  // frames. This remains exactly one Runtime RPC per L1 HTTP request while
  // retaining the independent 120 KiB mutation chunk ceiling beneath the
  // shared 4 MiB private transport envelope.
  rpc MutateRecords(stream MutateRecordsRequest) returns (MutateRecordsResponse);
  rpc GetMutationStatus(GetMutationStatusRequest) returns (GetMutationStatusResponse);
}

message MutationCommandHeader {
  // Command identity has exactly one authority: MutationCommand.command_id.
  // The transport header describes only the encoded byte stream and must not
  // introduce an admission/reconciliation identity of its own.
  reserved 1;
  reserved "command_id";
  uint64 encoded_command_bytes = 2;
  bytes encoded_sha256 = 3;
}

message MutationCommandChunk {
  bytes data = 1;
}

message MutateRecordsRequest {
  oneof frame {
    MutationCommandHeader header = 1;
    MutationCommandChunk chunk = 2;
  }
}

enum MutationBatchMode {
  MUTATION_BATCH_MODE_UNSPECIFIED = 0;
  MUTATION_BATCH_MODE_INDEPENDENT = 1;
  MUTATION_BATCH_MODE_ATOMIC = 2;
}

// Identifies the public mutation family whose Catalog-authored policy Runtime
// interprets. It is independent of transaction grouping. UNSPECIFIED exists
// only for N-1 compatibility and must be evaluated conservatively.
enum MutationUseCase {
  MUTATION_USE_CASE_UNSPECIFIED = 0;
  MUTATION_USE_CASE_SINGLE_RECORD = 1;
  MUTATION_USE_CASE_COLLECTION = 2;
}

enum MutationOperationKind {
  MUTATION_OPERATION_KIND_UNSPECIFIED = 0;
  MUTATION_OPERATION_KIND_CREATE = 1;
  MUTATION_OPERATION_KIND_UPDATE = 2;
  MUTATION_OPERATION_KIND_DELETE = 3;
  MUTATION_OPERATION_KIND_UPSERT = 4;
}

enum MutationValueKind {
  MUTATION_VALUE_KIND_UNSPECIFIED = 0;
  MUTATION_VALUE_KIND_STRING = 1;
  MUTATION_VALUE_KIND_BOOLEAN = 2;
  MUTATION_VALUE_KIND_INTEGER = 3;
  MUTATION_VALUE_KIND_DECIMAL = 4;
  MUTATION_VALUE_KIND_DATE = 5;
  MUTATION_VALUE_KIND_DATETIME = 6;
  MUTATION_VALUE_KIND_REFERENCE = 7;
  MUTATION_VALUE_KIND_NULL = 8;
  // Lossless caller JSON carriers. Runtime resolves their Catalog field type.
  MUTATION_VALUE_KIND_NUMBER = 9;
  MUTATION_VALUE_KIND_JSON = 10;
}

message MutationDecimalValue {
  string coefficient = 1;
  uint32 scale = 2;
}

message MutationDateValue {
  string calendar_date = 1;
}

message MutationDateTimeValue {
  string utc_rfc3339 = 1;
}

message MutationReferenceValue {
  string identifier = 1;
}

message MutationNullValue {}

// Exactly one value arm is present. Null is explicit. An omitted field has no
// MutationFieldValue entry. Pointer-preserving semantic mapping distinguishes
// false, zero, and the empty string from omission.
message MutationFieldValue {
  string name = 1;
  MutationValueKind declared_kind = 2;
  oneof value {
    string string_value = 3;
    bool boolean_value = 4;
    sint64 integer_value = 5;
    MutationDecimalValue decimal_value = 6;
    MutationDateValue date_value = 7;
    MutationDateTimeValue datetime_value = 8;
    MutationReferenceValue reference_value = 9;
    MutationNullValue null_value = 10;
    // Exact JSON number lexical text; never reconstructed from float64.
    string number_value = 11;
    // Canonical JSON object or array.
    string canonical_json_value = 12;
  }
}

message MutationOperation {
  MutationOperationKind kind = 1;
  string object_id = 2;
  string record_id = 3;
  // Canonical ascending bytewise UTF-8 field-name order on the wire.
  repeated MutationFieldValue fields = 4;
  MutationExternalIdentity external_identity = 5;
}

// MutationExternalIdentity is present only for UPSERT. Runtime resolves and
// validates the field against the same request-bound Catalog view as object
// shape and write permissions; L1 cannot supply a parallel metadata decision.
message MutationExternalIdentity {
  string field_id = 1;
  string value = 2;
}

// MutationCommand is the typed inner message carried by MutateRecordsRequest.
// The encoded byte digest in the header protects reassembly only. Runtime
// computes idempotency over the canonical semantic command after decoding.
message MutationCommand {
  string command_id = 1;
  MutationBatchMode batch_mode = 2;
  repeated MutationOperation operations = 3;
  optional uint64 min_catalog_revision = 4;
  MutationUseCase use_case = 5;
}

enum MutationErrorKind {
  MUTATION_ERROR_KIND_UNSPECIFIED = 0;
  MUTATION_ERROR_KIND_PROCESSING_HALTED = 1;
  MUTATION_ERROR_KIND_ATOMIC_OPERATION_ROLLED_BACK = 2;
  MUTATION_ERROR_KIND_INTERNAL = 3;
  MUTATION_ERROR_KIND_INVALID_FIELD_FOR_WRITE = 4;
  MUTATION_ERROR_KIND_CUSTOM_VALIDATION = 5;
  MUTATION_ERROR_KIND_INVALID_REFERENCE = 6;
  MUTATION_ERROR_KIND_FIELD_INTEGRITY = 7;
  MUTATION_ERROR_KIND_INVALID_OPERATION = 8;
  MUTATION_ERROR_KIND_INVALID_FIELD = 9;
  MUTATION_ERROR_KIND_MISSING_OR_DENIED = 10;
  MUTATION_ERROR_KIND_MALFORMED_IDENTIFIER = 11;
  MUTATION_ERROR_KIND_INVALID_IDENTIFIER = 12;
  MUTATION_ERROR_KIND_AUTOMATION = 13;
  // Object CRUD or field-write denial for this operation. Record existence
  // and RLS denial remain masked together as MISSING_OR_DENIED.
  MUTATION_ERROR_KIND_ACCESS_DENIED = 14;
  // Catalog-authored mutation policy denied this operation.
  MUTATION_ERROR_KIND_POLICY_DENIED = 15;
  // Canonical Catalog fields missing from a create caller obligation.
  MUTATION_ERROR_KIND_REQUIRED_FIELD_MISSING = 16;
  // The operation names an object absent from the one request-bound Catalog
  // revision. This is a command preflight cause, not record-existence masking.
  MUTATION_ERROR_KIND_OBJECT_NOT_FOUND = 17;
}

message MutationOperationError {
  MutationErrorKind kind = 1;
  // Catalog-authored caller-visible ValidationRule text. Legal only with
  // CUSTOM_VALIDATION; this is not a general internal error channel.
  string authored_message = 2;
  // Canonical unique ascending bytewise UTF-8 field-name order.
  repeated string fields = 3;
  reserved 4;
  reserved "authored_code";
  // Neutral metadata authority proving that direct create must be replaced by
  // one of the named action capabilities. L1 owns route and text projection.
  MutationCreateFromSourceGuard create_from_source_guard = 5;
}

message MutationCreateFromSourceGuard {
  string object_id = 1;
  // Canonical unique ascending bytewise UTF-8 action-identifier order.
  repeated string action_ids = 2;
}

message MutationOperationResult {
  uint32 index = 1;
  MutationOperationKind kind = 2;
  string record_id = 3;
  bool success = 4;
  optional bool created = 5;
  repeated MutationOperationError errors = 6;
}

message MutateRecordsResponse {
  string command_id = 1;
  // Exactly one position-correlated result per submitted operation.
  repeated MutationOperationResult results = 2;
  nexuscrm.common.v1.ConsistencyReceipts receipts = 3;
  string outcome_expires_at_utc = 4;
  // SHA-256 of the canonical semantic command. This is a derived content
  // binding, not a second command identity.
  bytes semantic_sha256 = 5;
}

message GetMutationStatusRequest {
  string command_id = 1;
  // Derived from the original accepted command. Runtime compares it with the
  // durable binding before returning any state; mismatch is masked as MISSING.
  bytes semantic_sha256 = 2;
}

enum MutationCommandState {
  MUTATION_COMMAND_STATE_UNSPECIFIED = 0;
  MUTATION_COMMAND_STATE_ACCEPTED = 1;
  MUTATION_COMMAND_STATE_EXECUTING = 2;
  MUTATION_COMMAND_STATE_COMPLETED = 3;
}

message GetMutationStatusResponse {
  string command_id = 1;
  MutationCommandState state = 2;
  // Empty before COMPLETED; complete and position-correlated at COMPLETED.
  repeated MutationOperationResult results = 3;
  // Absent before COMPLETED.
  nexuscrm.common.v1.ConsistencyReceipts receipts = 4;
  // Empty before COMPLETED. Unknown or expired commands return MISSING.
  string outcome_expires_at_utc = 5;
  // Present in every state and equal to the request binding.
  bytes semantic_sha256 = 6;
}
