{
  "$schema": "./comment-profile.v1.schema.json",
  "contractVersion": "1.0.0",
  "topic": "report-comment-thread-control-plane",
  "availability": "desktop_local_runtime",
  "approvedOn": "2026-08-20",
  "authority": {
    "decisionRecords": [
      "RFC-011",
      "RFC-008",
      "RFC-006",
      "RFC-010"
    ],
    "machineProfilePath": "Report_Doc/docs/public/contracts/comment-profile.v1.json",
    "machineProfileSchemaPath": "Report_Doc/docs/public/contracts/comment-profile.v1.schema.json",
    "implementationPaths": [
      "pipelineXlab_pxflow/crates/pxflow-core/src/comments.rs",
      "pipelineXlab_pxflow/crates/pxflow-runtime-application/src/comment_threads.rs",
      "pipelineXlab_pxflow/crates/pxflow-local-adapters/src/lib.rs",
      "pipelineXlab_pxflow/crates/pxflow-runtime/src/comment_threads.rs"
    ]
  },
  "records": {
    "CommentOriginTarget": {
      "members": [
        "workspaceRef",
        "projectRef",
        "reportKey",
        "revisionRef",
        "blockAnchor",
        "inlineSelector"
      ],
      "immutable": true
    },
    "CommentThread": {
      "members": [
        "threadKey",
        "origin",
        "creatorRef",
        "createdAt",
        "reviewRequestRef"
      ],
      "immutable": true
    },
    "CommentEntry": {
      "members": [
        "entryKey",
        "threadKey",
        "authorRef",
        "createdAt",
        "body",
        "parentEntryKey"
      ],
      "immutable": true
    },
    "CommentRedaction": {
      "members": [
        "redactionKey",
        "threadKey",
        "targetEntryKey",
        "actorRef",
        "redactedAt",
        "reason"
      ],
      "immutable": true
    },
    "CommentThreadState": {
      "members": [
        "threadKey",
        "resolutionState",
        "assigneeRef",
        "generation"
      ],
      "immutable": false
    },
    "CommentThreadStateTransition": {
      "members": [
        "transitionKey",
        "threadKey",
        "fromState",
        "toState",
        "actorRef",
        "occurredAt",
        "expectedGeneration",
        "resultingGeneration"
      ],
      "immutable": true
    }
  },
  "operations": [
    {
      "name": "product.report.list_comment_threads",
      "kind": "public-read",
      "requiredCapability": "comment.read"
    },
    {
      "name": "AddCommentThread",
      "kind": "repository-write",
      "requiredCapability": "comment.write"
    },
    {
      "name": "AddCommentReply",
      "kind": "repository-write",
      "requiredCapability": "comment.write"
    },
    {
      "name": "ResolveCommentThread",
      "kind": "repository-write",
      "requiredCapability": "comment.resolve"
    },
    {
      "name": "ReopenCommentThread",
      "kind": "repository-write",
      "requiredCapability": "comment.resolve"
    },
    {
      "name": "RedactCommentEntry",
      "kind": "repository-write",
      "requiredCapability": "comment.resolve"
    }
  ],
  "persistence": {
    "schemaVersion": 8,
    "tables": [
      "pxflow_comment_thread",
      "pxflow_comment_entry",
      "pxflow_comment_redaction",
      "pxflow_comment_thread_state",
      "pxflow_comment_thread_state_transition"
    ],
    "storageBoundary": "relational-control-plane-outside-report-body",
    "migrationMode": "additive",
    "rollback": "disable product composition, leave comment data intact, do not down-drop tables and do not rewrite artifact bytes or revision history",
    "serverAndPostgresComposition": "not-applicable-until-rfc012-server-authoring-repository-composition-is-approved"
  },
  "identityAndOrigin": {
    "identityForm": "SemanticKey",
    "originIsImmutable": true,
    "anchorAuthority": "RFC-011 stable internal Report block anchor validated through the existing Report anchor validator",
    "inlineSelectorRule": "revision-bound and uniquely resolved at the immutable origin revision",
    "orphanRule": "orphaned is a derived read projection and is never persisted as a resolution state",
    "reviewRequestReference": "optional opaque exact reference only; no ReviewRequest aggregate or new identity kind is defined",
    "generatedResultAnchors": {
      "identity": "table key, stable row key or Batch case key, and fieldKey; header identity uses a null row, never a column index",
      "encoding": "result_ + lowercase SHA-256 hex of UTF-8 compact JSON [table,row-or-null,fieldKey] + _ + lowercase hex of a 16-byte incarnation token; the SDK authoring issuer mints it once from OS entropy, and the Runtime projection issuer derives it as the first 16 bytes of SHA-256 of UTF-8 compact JSON [\"report-result-incarnation-v1\",workspaceRef,projectRef,reportKey,runRef,requestDigest] of the first projection command",
      "incarnation": "newly generated cells and headers get a fresh incarnation; deletion followed by regeneration never reuses the prior anchor even for the same table, row or case, and fieldKey; a pre-commit Runtime retry of the same projection command identity recomputes the same incarnation",
      "refresh": "existing cells matched by stable row or case and fieldKey, and retained headers, preserve their exact anchors including legacy opaque anchors",
      "nonReuse": "an anchor is never reassigned to another logical cell; unresolved origins are not automatically attached to regenerated cells",
      "compatibility": "no rewrite of immutable CommentThread origins or stored revisions; no change to bodyDocument schemaVersion or comment storage; opaque anchors do not impose a shared grammar on all SemanticKeys",
      "fixturePath": "Report_Doc/docs/public/contracts/fixtures/result-comment-anchors.v1.json",
      "draftStability": "mint the token once during creation and retain it in the draft; repeated serialization and command retries preserve exact bytes, while independent creation has a distinct incarnation"
    }
  },
  "state": {
    "persistedResolutionStates": [
      "open",
      "resolved"
    ],
    "effectiveReadStates": [
      "open",
      "resolved",
      "orphaned"
    ],
    "transitionRule": "resolve and reopen compare-and-swap expected generation, update current state and append one immutable transition in the same transaction"
  },
  "entries": {
    "editOperation": "not-a-v1-feature",
    "correctionRule": "append a new CommentEntry reply; never mutate the earlier entry",
    "removalRule": "append a separate CommentRedaction; never mutate or delete the original entry",
    "hardPurgeAndRetention": "unapproved"
  },
  "authorization": {
    "capabilities": [
      "comment.read",
      "comment.write",
      "comment.resolve"
    ],
    "visibilityRule": "every operation also requires an active effective AccessGrant for the exact workspace and project",
    "creatorIsAuthorizationAuthority": false
  },
  "pagination": {
    "ordering": [
      "createdAt",
      "threadKey"
    ],
    "cursor": "opaque and bound to workspace, project, report and current revision",
    "minimumPageSize": 1,
    "maximumPageSize": 500
  },
  "artifactBoundary": {
    "reportBodyContainsComments": false,
    "copyOrExportClonesComments": false,
    "artifactBytesOrRevisionHistoryMayBeRewritten": false
  },
  "auditAndActivity": {
    "rfc006AuditActionClassCount": 12,
    "successfulCommentOperationsWriteRfc006Audit": false,
    "threadActivityHistory": "CommentThreadStateTransition",
    "domainEventPayloadContract": "unapproved"
  },
  "toolBoundary": {
    "fixedToolCount": 21,
    "publicReadIsTool": false,
    "toolCatalogChanged": false
  },
  "deferred": {
    "reviewRequestContractBlocker": "rfc011-review-request-contract",
    "sharingAggregateIdentityBlocker": "rfc011-sharing-aggregate-identity-kinds",
    "domainEventRetentionBlocker": "rfc011-v2-domain-event-contracts",
    "serverCompositionBlocker": "rfc012-server-authoring-repository-composition"
  }
}
