import type { CollectionConfig } from "payload"; import { requirePermission } from "@/utils/access-control/hasPermission"; /** * Role-scoped, advisory operation readiness + planning-confidence aggregates. * * One row per (subjectType, subjectId, windowStart). Writes are internal only * (create/update/delete denied; the readiness service writes via `overrideAccess` * with its own permission gate). Read is gated by the NEW * `operation-readiness:read` permission, so it is role-scoped, not open to any * logged-in user. * * The stored row carries only normalized scores, category weights, and input * counts plus a generated explanation. It never contains raw evaluation content * (evaluator names, rater ids, or comment text), which is what the privacy * guarantee rests on. */ export const OperationReadiness: CollectionConfig = { slug: "operation-readiness", admin: { group: "Operations", useAsTitle: "subjectLabel", defaultColumns: ["subjectType", "subjectLabel", "windowStart", "readinessScore", "planningConfidenceScore"], }, access: { read: requirePermission("operation-readiness:read"), create: () => false, update: () => false, delete: () => false, }, fields: [ { name: "subjectType", type: "select", required: true, options: [ { label: "User", value: "user" }, { label: "Role", value: "role" }, ], admin: { description: "Who the aggregate is for: a specific user, or a whole role.", }, }, { name: "subjectId", type: "number", admin: { description: "Id of the subject (user id for user-scoped; null for role-scoped).", }, }, { name: "subjectLabel", type: "text", admin: { description: "Human-readable label for the subject (role name or username). Display only.", }, }, { name: "windowStart", type: "date", required: true, admin: { description: "Start of the explicit time window this aggregate covers.", }, }, { name: "windowEnd", type: "date", admin: { description: "End of the time window.", }, }, { name: "windowLabel", type: "text", admin: { description: "Optional human label for the window (e.g. an ISO week).", }, }, { name: "operationId", type: "text", admin: { description: "Optional operation identity the window's data is attributed to.", }, }, { name: "calculationVersion", type: "text", required: true, defaultValue: "1.0.0", admin: { description: "Readiness calculation model version.", }, }, { name: "nature", type: "select", required: true, defaultValue: "advisory", options: [{ label: "Advisory (non-gating)", value: "advisory" }], admin: { description: "Advisory-only. Never used to gate, punish, or rank players against one another.", }, }, { name: "readinessScore", type: "number", admin: { description: "Weighted 0-100 readiness score over present contributing categories.", }, }, { name: "planningConfidenceScore", type: "number", admin: { description: "Weighted 0-100 planning-confidence sub-score from pre-op signals.", }, }, { name: "contributingCategories", type: "json", admin: { description: "Structured per-category breakdown ({ category, weight, score, inputCount }). No evaluator data.", }, }, { name: "inputCount", type: "number", admin: { description: "Total component inputs consumed by the aggregate.", }, }, { name: "explanation", type: "textarea", admin: { description: "Human-readable explanation enumerating contributing categories and weights. Never contains evaluator names, ids, or comments.", }, }, { name: "recomputedAt", type: "date", admin: { description: "When the aggregate was last computed.", }, }, ], };