@ttt-productions/ttt-core 0.79.91 → 0.79.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/doc-schemas/commissions.d.ts +6 -0
  2. package/dist/doc-schemas/commissions.d.ts.map +1 -1
  3. package/dist/doc-schemas/commissions.js +5 -3
  4. package/dist/doc-schemas/commissions.js.map +1 -1
  5. package/dist/doc-schemas/media-assets.d.ts +101 -6
  6. package/dist/doc-schemas/media-assets.d.ts.map +1 -1
  7. package/dist/doc-schemas/media-assets.js +29 -19
  8. package/dist/doc-schemas/media-assets.js.map +1 -1
  9. package/dist/doc-schemas/notification-ledger.d.ts +99 -5
  10. package/dist/doc-schemas/notification-ledger.d.ts.map +1 -1
  11. package/dist/doc-schemas/notification-ledger.js +94 -16
  12. package/dist/doc-schemas/notification-ledger.js.map +1 -1
  13. package/dist/doc-schemas/registry.d.ts +29 -1
  14. package/dist/doc-schemas/registry.d.ts.map +1 -1
  15. package/dist/media/index.d.ts +1 -0
  16. package/dist/media/index.d.ts.map +1 -1
  17. package/dist/media/index.js +1 -0
  18. package/dist/media/index.js.map +1 -1
  19. package/dist/media/publication/index.d.ts +2 -0
  20. package/dist/media/publication/index.d.ts.map +1 -0
  21. package/dist/media/publication/index.js +2 -0
  22. package/dist/media/publication/index.js.map +1 -0
  23. package/dist/media/publication/publication-args.d.ts +541 -0
  24. package/dist/media/publication/publication-args.d.ts.map +1 -0
  25. package/dist/media/publication/publication-args.js +184 -0
  26. package/dist/media/publication/publication-args.js.map +1 -0
  27. package/dist/media/target-info.d.ts.map +1 -1
  28. package/dist/media/target-info.js +4 -5
  29. package/dist/media/target-info.js.map +1 -1
  30. package/dist/paths/collection-groups.d.ts +1 -0
  31. package/dist/paths/collection-groups.d.ts.map +1 -1
  32. package/dist/paths/collection-groups.js +1 -0
  33. package/dist/paths/collection-groups.js.map +1 -1
  34. package/dist/schemas/admin.d.ts +8 -2
  35. package/dist/schemas/admin.d.ts.map +1 -1
  36. package/dist/schemas/admin.js +10 -8
  37. package/dist/schemas/admin.js.map +1 -1
  38. package/dist/schemas/hall-library.d.ts +10 -3
  39. package/dist/schemas/hall-library.d.ts.map +1 -1
  40. package/dist/schemas/hall-library.js +30 -24
  41. package/dist/schemas/hall-library.js.map +1 -1
  42. package/dist/schemas/notification.d.ts +1 -1
  43. package/dist/schemas/users.d.ts +5 -0
  44. package/dist/schemas/users.d.ts.map +1 -1
  45. package/dist/schemas/users.js +15 -11
  46. package/dist/schemas/users.js.map +1 -1
  47. package/dist/upload-variables/create-admin-audition-variables.d.ts.map +1 -1
  48. package/dist/upload-variables/create-admin-audition-variables.js +2 -1
  49. package/dist/upload-variables/create-admin-audition-variables.js.map +1 -1
  50. package/dist/upload-variables/create-work-project-audition-variables.d.ts.map +1 -1
  51. package/dist/upload-variables/create-work-project-audition-variables.js +2 -1
  52. package/dist/upload-variables/create-work-project-audition-variables.js.map +1 -1
  53. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"media-assets.js","sourceRoot":"","sources":["../../src/doc-schemas/media-assets.ts"],"names":[],"mappings":"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","sourcesContent":["// mediaAssets/{mediaAssetId} — the canonical media registry. One doc per\n// logical uploaded media item; all variants inside. Content docs reference\n// media ONLY by `*AssetId` fields — never URLs. Serving authority is the\n// gateway Worker (cookie/grant + KV blocklist); this doc is the server-side\n// truth the edge records derive from. NO client reads, NO client writes.\n// See ttt-prod docs/design/media-assets-and-protected-serving.md.\n\nimport { z } from 'zod';\nimport { StructuredErrorSchema } from '@ttt-productions/edge-protocol-core';\nimport { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_OUTPUT_KEY } from '@ttt-productions/media-schemas';\nimport { FileOriginSchema } from '../media/file-origin.js';\n\n/** Who may be served the bytes (the Worker's tier check). */\nexport const MediaAccessTierSchema = z.enum(['broad', 'scoped', 'artisan', 'adminOnly']);\nexport type MediaAccessTier = z.infer<typeof MediaAccessTierSchema>;\n\n/** Canonical serving state. The edge record derives from this; hide/restore/\n * quarantine flows write it together with the blocklist. */\nexport const MediaServingStatusSchema = z.enum(['servable', 'hidden', 'quarantined', 'deleted']);\nexport type MediaServingStatus = z.infer<typeof MediaServingStatusSchema>;\n\n/** The surface that owns this asset. One asset never serves two scopes —\n * publishing to the Hall (or future realm promotion) creates a NEW asset\n * owned by the destination surface. */\nexport const MediaAssetOwnerTypeSchema = z.enum([\n 'userProfile',\n 'craftSkill',\n 'squareStreetzPost',\n 'workProject',\n 'workContent', // tale chapter / tune track / television episode media (pre-publish)\n 'hallItem',\n 'commissionListing',\n 'commissionProposal',\n 'audition',\n 'auditionEntry',\n // A file in a conversation's flat Conversation Files list (guild-invite or\n // admin-support). Replaces the removed `guildChatAttachment` owner type — chat\n // messages no longer own media.\n 'conversationFile',\n // Realm shared files are the deliberate MUTATE-IN-PLACE exception: a promoted work file\n // keeps its ONE `workFile`-owned asset doc and flips tier in place, so no asset is ever\n // owned by `realmFile` today. The owner type stays declared for a future realm-owned\n // media surface; it is NOT the seam realm sharing rides (that is `realmFileCanonStatus`\n // + `realmId` + `realmFileFolderId` on the work file's own asset).\n 'realmFile',\n 'safetyEvidence', // inert system sentinel — synthetic NCII-evidence assets (never an account surface)\n // An admin video held by a system video slot; the owner id is the slot id.\n 'systemContent',\n]);\nexport type MediaAssetOwnerType = z.infer<typeof MediaAssetOwnerTypeSchema>;\n\n// ===== Media origin lineage (Appendix A0) =====\n// One canonical lineage used by media, CSAM, and safety. Embedded on every\n// media asset + every copy.\n\n/** The ONE canonical stored content media kind (what the pipeline produces and the\n * asset doc records). Distinct from the 4-value display/transport MediaTypeSchema\n * (doc-schemas/social.ts, adds 'other') and media-schemas' generic MediaKindSchema. */\nexport const ContentMediaKindSchema = z.enum(['image', 'video', 'audio']);\nexport type ContentMediaKind = z.infer<typeof ContentMediaKindSchema>;\n\n/**\n * Every variant key the media pipeline writes under an asset id — never an open `string`: the image\n * variants `TTT_MEDIA_SPECS` declares, plus the video and audio outputs, which derive from the\n * pipeline's own output-key declarations (media-schemas) — the transcoded `main` and the video's\n * `poster` frame. A copy or an ingest intent names its objects with these, so a key outside the set\n * could be written but never reclaimed. `__tests__/media-origin-lineage.test.ts` fails when a spec\n * declares a key outside the set, a pipeline output falls outside it, or a member is produced by\n * nothing.\n */\nexport const MEDIA_VARIANT_KEYS = [\n 'full',\n 'medium',\n 'small',\n TIMED_MEDIA_MAIN_OUTPUT_KEY,\n VIDEO_POSTER_OUTPUT_KEY,\n] as const;\nexport const MediaVariantKeySchema = z.enum(MEDIA_VARIANT_KEYS);\nexport type MediaVariantKey = z.infer<typeof MediaVariantKeySchema>;\n\n/** Why this asset was created from its source (A0).\n *\n * `chat_derivative` was REMOVED with the chat-attachment architecture: chat is\n * text-only and owns no media, so no copy can be made \"for a chat message\". A\n * Conversation File is an ORIGINAL (`original`), never a chat derivative; nothing\n * ever set the retired value. */\nexport const MediaCopyReasonSchema = z.enum([\n 'original',\n 'variant',\n 'hall_publish',\n 'profile_derivative',\n 'moderation_copy',\n 'evidence_copy',\n 'other',\n]);\nexport type MediaCopyReason = z.infer<typeof MediaCopyReasonSchema>;\n\n/**\n * Canonical media origin lineage (Appendix A0). A copy INHERITS\n * rootIngestId/rootAssetId/originatingUploaderUid/originatingUploadEventId/\n * originalUploadCreatedAt/rootSha256/originalSha256 unchanged; it sets its own\n * sourceAssetId/variantSha256s/copyReason/copyActorUid/currentOwner*. Client\n * requests can NEVER set or override any lineage field. Automatic account\n * action targets ONLY `originatingUploaderUid`; incident-wide blocking\n * enumerates by `rootIngestId`. Missing/corrupt lineage → safetyLocked +\n * manual decision, never a guessed ban.\n */\nexport const MediaOriginLineageV1Schema = z.object({\n lineageVersion: z.literal(1),\n rootIngestId: z.string().min(1), // first-accepted ingest event id; stable across all copies\n rootAssetId: z.string().min(1), // first asset created from that ingest\n sourceAssetId: z.string().min(1).optional(), // immediate parent copied from (absent on the root)\n originatingUploaderUid: z.string().min(1), // set server-side at first accepted ingest; IMMUTABLE; inherited\n // May be '' for a legacy copy synthesized from a pre-rollout source with no known upload event.\n originatingUploadEventId: z.string(),\n originalUploadCreatedAt: z.number(),\n // '' until content hashing backfills the real sha256 (Phase 5 PhotoDNA / evidence capture).\n // NOT z.string().min(1): the media pipeline does not compute a content hash at finalize yet,\n // so a freshly-ingested asset legitimately carries an empty rootSha256 until it is hashed.\n rootSha256: z.string(),\n originalSha256: z.string().min(1).optional(),\n // PARTIAL map: one hash per PRODUCED variant (an asset produces only the variants its\n // origin declares — e.g. full/medium/small for a profile picture, 'main' for others —\n // never all four). Typed keys, never an open string. `z.partialRecord` (NOT `z.record`):\n // in Zod 4 `z.record(enum,…)` is EXHAUSTIVE (demands every enum key), which would reject\n // every real asset's partial variant set; `partialRecord` makes the keys optional.\n variantSha256s: z.partialRecord(MediaVariantKeySchema, z.string().min(1)),\n copyReason: MediaCopyReasonSchema,\n createdFromOwnerType: MediaAssetOwnerTypeSchema.optional(), // never a bare string\n createdFromOwnerId: z.string().min(1).optional(),\n copyActorUid: z.string().min(1).optional(), // who triggered the copy (distinct from the original uploader)\n currentOwnerType: MediaAssetOwnerTypeSchema, // mutable; the asset's present owner\n currentOwnerId: z.string().min(1),\n}).strict();\nexport type MediaOriginLineageV1 = z.infer<typeof MediaOriginLineageV1Schema>;\n\nexport const MediaAssetVariantSchema = z.object({\n contentType: z.string().min(1),\n sizeBytes: z.number().int().nonnegative(),\n width: z.number().int().positive().optional(),\n height: z.number().int().positive().optional(),\n durationSec: z.number().positive().optional(),\n /**\n * Server-owned filename the gateway puts in `Content-Disposition` for a\n * `?download=1` request. VARIANT-specific on purpose: the downloadable bytes are\n * a PROCESSED variant, so the extension must match THIS variant's actual\n * `contentType` — a transcoded JPEG/WebP/MP4 is never labelled with the original\n * upload's incompatible extension.\n *\n * Written ONLY by backend publishers, and ONLY for surfaces that intentionally\n * offer a Download action — Work Files (including the same asset shared to a\n * Realm), Conversation Files, and downloadable Hall media. Profiles, posts,\n * covers, and every other surface leave it ABSENT so no private original filename\n * is ever exposed; an absent value serves the safe bare `attachment` disposition.\n *\n * Always produced by `normalizeDownloadFilename` (media/download-filename.ts) —\n * that helper owns the sanitization rules and the UTF-8 byte cap, so no bound is\n * restated here. A client query parameter is NEVER the filename authority.\n * Filenames are user content: never log this value.\n */\n downloadFilename: z.string().min(1).optional(),\n}).strict();\nexport type MediaAssetVariant = z.infer<typeof MediaAssetVariantSchema>;\n\nexport const MediaRetentionPolicySchema = z.enum([\n 'standard', // staging original deleted ~24h after verified mirror\n 'preserveForTrustAndSafety',\n 'legalHold',\n]);\nexport type MediaRetentionPolicy = z.infer<typeof MediaRetentionPolicySchema>;\n\n// Realm shared-files: a work file's standing in its realm's shared pool. ONE seam — the\n// approval gate is a value in this union, never a parallel status field.\n// none — not shared to the realm (default)\n// pendingApproval — a Work file admin has REQUESTED promotion; the Realm steward has not\n// decided yet. The file is NOT served to the Realm (tier stays `scoped`)\n// and is invisible to the member gallery, whose query selects only\n// nonCanon|canon — so pending rows are excluded structurally, not by a\n// filter someone can forget.\n// nonCanon — approved into the realm's pool (in a steward-chosen folder), non-canon\n// canon — approved and marked canon (realm steward toggles canon ↔ nonCanon)\n// Moderation/takedown is NOT a value here — a hidden file is hidden via the shared\n// media path (`servingStatus: 'hidden'`), so the gallery filters on servingStatus.\n//\n// Each member is declared ONCE as a named constant below and every set is assembled from\n// those names, so a member constant and the sets that contain it cannot drift apart.\n\n/** Not shared to the realm — the default standing for every work file. */\nexport const REALM_FILE_NONE_STATUS = 'none' as const;\n/** The single AWAITING-DECISION standing. Exported as a VALUE as well as a schema so a\n * consumer can branch on it (`status === REALM_FILE_PENDING_APPROVAL_STATUS`) without\n * re-quoting the member — the redeclaration guard pins this literal to this file. */\nexport const REALM_FILE_PENDING_APPROVAL_STATUS = 'pendingApproval' as const;\n/** Approved into the realm's shared pool, not marked canon. */\nexport const REALM_FILE_NON_CANON_STATUS = 'nonCanon' as const;\n/** Approved into the realm's shared pool AND marked canon by the Realm steward. */\nexport const REALM_FILE_CANON_STATUS = 'canon' as const;\n\nexport const REALM_FILE_CANON_STATUS_VALUES = [\n REALM_FILE_NONE_STATUS,\n REALM_FILE_PENDING_APPROVAL_STATUS,\n REALM_FILE_NON_CANON_STATUS,\n REALM_FILE_CANON_STATUS,\n] as const;\nexport const RealmFileCanonStatusSchema = z.enum(REALM_FILE_CANON_STATUS_VALUES);\nexport type RealmFileCanonStatus = z.infer<typeof RealmFileCanonStatusSchema>;\n\n/** The two APPROVED (steward-accepted, realm-served) standings. The member gallery query\n * and its projection accept exactly these — never the full status union, so a pending or\n * unshared row can never be typed into an approved-files response. Derived from the one\n * union above; consumers import this instead of re-quoting the members (ARCH-102). */\nexport const REALM_FILE_APPROVED_STATUS_VALUES = [\n REALM_FILE_NON_CANON_STATUS,\n REALM_FILE_CANON_STATUS,\n] as const;\nexport const RealmFileApprovedStatusSchema = z.enum(REALM_FILE_APPROVED_STATUS_VALUES);\nexport type RealmFileApprovedStatus = z.infer<typeof RealmFileApprovedStatusSchema>;\n\n/** The steward promotion-queue projection accepts only this, so an approved or unshared row\n * can never be typed into the queue. */\nexport const RealmFilePendingApprovalStatusSchema = z.literal(REALM_FILE_PENDING_APPROVAL_STATUS);\nexport type RealmFilePendingApprovalStatus = z.infer<typeof RealmFilePendingApprovalStatusSchema>;\n\n/** Every standing EXCEPT `none` — a file that has entered the request/approval lifecycle at\n * all. Derived from the two subsets above, so adding a future standing to one of them\n * reaches this set automatically. Used by the Work-side share-state projection, whose rows\n * exist only for files that are actually requested or shared. */\nexport const REALM_FILE_ACTIVE_STATUS_VALUES = [\n REALM_FILE_PENDING_APPROVAL_STATUS,\n ...REALM_FILE_APPROVED_STATUS_VALUES,\n] as const;\nexport const RealmFileActiveStatusSchema = z.enum(REALM_FILE_ACTIVE_STATUS_VALUES);\nexport type RealmFileActiveStatus = z.infer<typeof RealmFileActiveStatusSchema>;\n\n// ===== Serving authority (Durable Object) + publication gating =====\n// See ttt-prod docs/design/media-assets-and-protected-serving.md (the design owner\n// for the Durable Object serving authority and publication gating).\n\n/** Whether the canonical serving record has been applied to the DO authority. */\nexport const MediaAuthoritySyncStateSchema = z.enum(['pending', 'applied', 'deadLetter']);\nexport type MediaAuthoritySyncState = z.infer<typeof MediaAuthoritySyncStateSchema>;\n\n/** Whether the asset is referenced by a visible owner doc. Orthogonal to the\n * upload's processing outcome (`pendingMedia.status`) and to `servingStatus`. */\nexport const MediaAssetPublicationStateSchema = z.enum(['unpublished', 'published', 'retired']);\nexport type MediaAssetPublicationState = z.infer<typeof MediaAssetPublicationStateSchema>;\n\n/**\n * Typed immutable serving scope for scoped-tier assets. The gateway Worker matches\n * a grant against the scope by `kind` — so a whole-Work `{w}` grant matches ONLY a\n * `workProject` scope, never a conversation scope, and a conversation grant is never\n * reachable by a Work asset.\n *\n * - `workProject` — pre-publish content media (hall covers / sub-item media during\n * authoring), scoped to project read membership. Carries the matchable\n * `workProjectId`. This is the typed home for work scope on the strongly-consistent\n * serving record so the DO-fallback path can authorize it (the DO persists scope\n * only as `scopeJson`, no separate column).\n * - `workFileFolder` — a work-project FILE living in a folder. The EXACT folder scope,\n * NOT the whole-Work `{w}` grant: a custom folder's bytes must be unreachable by a\n * guildmate outside that folder's view trade-professions, so the grant callable runs\n * the per-folder view check and the Worker EXACT-matches `{workProjectId,\n * workFileFolderId}` — a plain Work `{w}` grant NEVER matches a folder file, and a\n * folder grant NEVER matches a `workProject`-scoped content asset.\n * - `guildInvite` / `adminSupport` — the two Conversation Files scopes; an EXACT\n * conversation scope, matching the `ConversationFileRef` that owns the file. There is\n * NO guildChannel scope: guild chat channels have no Conversation Files (guildmates\n * share files through Work Files, which keeps its own workFileFolder scope).\n *\n * `null` scope = no scope match required beyond the access tier (e.g. broad).\n */\nexport const MediaServingScopeSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('workProject'),\n workProjectId: z.string().min(1),\n }).strict(),\n z.object({\n kind: z.literal('workFileFolder'),\n workProjectId: z.string().min(1),\n workFileFolderId: z.string().min(1),\n }).strict(),\n z.object({\n kind: z.literal('guildInvite'),\n guildInviteId: z.string().min(1),\n }).strict(),\n z.object({\n kind: z.literal('adminSupport'),\n adminDispatchId: z.string().min(1),\n }).strict(),\n]);\nexport type MediaServingScope = z.infer<typeof MediaServingScopeSchema>;\n\n/**\n * The typed owner adapter a publication goes through. The activation job\n * carries this kind; a server-side registry validates per-kind `publicationArgs`\n * and performs the idempotent owner write. Every kind is a Firestore-owner adapter.\n */\nexport const MediaPublicationKindSchema = z.enum([\n 'profilePicture',\n 'squarePostMedia',\n 'workAsset',\n 'hallCover',\n // Realm cover — Firestore-owner adapter writes `realmCoverAssetId` onto\n // workRealms/{workRealmId} (the 'realm-cover' upload origin).\n 'realmCover',\n 'workContentMedia', // sub-item media (tale chapter / tune track / television episode) — attaches the *AssetId field on the workContent doc; scoped to the work project\n 'auditionMedia',\n 'commissionListingMedia',\n 'commissionProposalMedia',\n 'craftSkillMedia',\n // Conversation File: the ONE adapter that creates a visible Conversation File owner\n // record ({conversationParent}/conversationFiles/{conversationFileId}) and transfers\n // the quota reservation to published counters in the same transaction. Replaces the\n // removed `chatAttachment` (Channel-DO flip) and `adminSupportAttachment` (message\n // placeholder flip) kinds. Carries the typed `guildInvite` / `adminSupport`\n // MediaServingScope of the owning conversation.\n 'conversationFile',\n // Points a system video slot at its new video and retires the video it replaces, in one\n // transaction.\n 'systemVideoSlot',\n]);\nexport type MediaPublicationKind = z.infer<typeof MediaPublicationKindSchema>;\n\n/**\n * The normalized serving record — the SAME contract used to generate both\n * the DO authority row and the KV cache payload (one function, so they can't\n * drift). The R2 object key stays deterministic (`mediaAssets/{assetId}/{key}`)\n * and is NOT persisted here. `payloadHash` is computed over the canonical\n * payload EXCLUDING the hash itself (see edge-protocol-core `hashPayload`).\n */\nexport const MediaServingAuthorityRecordSchema = z.object({\n schemaVersion: z.number().int().positive(),\n assetId: z.string().min(1),\n authorityVersion: z.number().int().nonnegative(),\n operationId: z.string().min(1),\n payloadHash: z.string().min(1),\n\n servingStatus: MediaServingStatusSchema,\n accessTier: MediaAccessTierSchema,\n\n ownerType: MediaAssetOwnerTypeSchema,\n ownerId: z.string().min(1),\n scope: MediaServingScopeSchema.nullable(),\n\n variants: z.record(z.string(), MediaAssetVariantSchema),\n\n updatedAtMs: z.number(),\n}).strict();\nexport type MediaServingAuthorityRecord = z.infer<typeof MediaServingAuthorityRecordSchema>;\n\n/**\n * Why the retirement drain last deferred an owed retirement: `held` — a legal or safety hold\n * blocks the byte removal; `failed` — an edge step or an object delete failed.\n */\nexport const MediaAssetRetirementDeferralSchema = z.enum(['held', 'failed']);\nexport type MediaAssetRetirementDeferral = z.infer<typeof MediaAssetRetirementDeferralSchema>;\n\n/**\n * The retirement obligation of an asset whose owner record is gone — written onto the asset doc in\n * the SAME transaction or batch as the delete (or refusal) that leaves it unowned, together with the\n * durable authority deny when the asset is still servable (servingStatusOnRetirementRequest), so no\n * path deletes a record and then retires its media best-effort. One\n * scheduled drain owns the byte removal: it takes up due obligations in `nextAttemptAt` order, and\n * the asset becomes `retired` only when every variant's object delete has succeeded. A hold or a\n * failure defers the obligation (`attemptCount`, `nextAttemptAt`, `lastDeferral`); it is never\n * dropped. The drain deletes the obligation in the transaction that marks the asset retired.\n */\nexport const MediaAssetRetirementSchema = z\n .object({\n /** Epoch ms the obligation was co-written with the delete. */\n requestedAt: z.number().int().nonnegative(),\n /** Variant keys whose object delete has not yet succeeded (an already-absent object counts as\n * deleted). Starts as every key of `variants`; the drain removes a key only after its delete\n * succeeds. */\n pendingVariantKeys: z.array(z.string().min(1)),\n attemptCount: z.number().int().nonnegative(),\n /** Epoch ms from which the drain may take the obligation up; the drain lists due ones in this\n * order, so a deferred one rotates behind the ones that can finish. */\n nextAttemptAt: z.number().int().nonnegative(),\n lastDeferral: MediaAssetRetirementDeferralSchema.optional(),\n lastError: StructuredErrorSchema.optional(),\n })\n .strict()\n .superRefine((val, ctx) => {\n if (new Set(val.pendingVariantKeys).size !== val.pendingVariantKeys.length) {\n ctx.addIssue({ code: 'custom', path: ['pendingVariantKeys'], message: 'a variant key is owed at most once' });\n }\n if (val.lastError !== undefined && val.lastDeferral !== 'failed') {\n ctx.addIssue({ code: 'custom', path: ['lastError'], message: 'an error is recorded only for a failed attempt' });\n }\n });\nexport type MediaAssetRetirement = z.infer<typeof MediaAssetRetirementSchema>;\n\nexport const MediaAssetSchema = z.object({\n mediaAssetId: z.string().min(1),\n mediaKind: ContentMediaKindSchema,\n fileOrigin: FileOriginSchema,\n\n // Ownership / scope. ownerId is the owning doc's id within ownerType's\n // surface; scope ids let the Worker/grant layer match scoped access.\n ownerType: MediaAssetOwnerTypeSchema,\n ownerId: z.string().min(1),\n workProjectId: z.string().optional(),\n realmId: z.string().optional(),\n createdByUid: z.string().min(1),\n\n // Access + serving (canonical; edge records derive from these).\n accessTier: MediaAccessTierSchema,\n servingStatus: MediaServingStatusSchema,\n\n // Variants by key. The object key in R2 (and the Storage emulator) is\n // deterministic — mediaAssets/{mediaAssetId}/{variantKey} — never stored.\n variants: z.record(z.string(), MediaAssetVariantSchema),\n\n // Lifecycle / trust-and-safety.\n moderationStatus: z.enum(['approved', 'quarantined']),\n retentionPolicy: MediaRetentionPolicySchema,\n legalHold: z.boolean(),\n originalDeletedAt: z.number().optional(),\n\n // T&S provenance lineage (Appendix A0). Additive/optional so assets written\n // before the lineage rollout still parse; set server-side at first accepted\n // ingest, inherited unchanged by every copy/variant. Never client-supplied.\n originLineage: MediaOriginLineageV1Schema.optional(),\n\n // ===== Realm shared-files seam (request → steward approval → foldered pool) =====\n // A file's standing in its realm's shared pool; set at creation to 'none' and moved\n // ONLY by the realm shared-files callables. The legal combinations of this field with\n // `realmId`, `realmFileFolderId`, and the three request fields are ENFORCED by the\n // refinement below — the fields are not independently optional.\n realmFileCanonStatus: RealmFileCanonStatusSchema,\n\n // The steward-chosen folder under `workRealms/{realmId}/realmFileFolders/{id}`. There is\n // no default folder: approval IS the folder assignment, so every approved file is in a\n // folder by construction. Cleared on decline, withdrawal, and admin un-share — but\n // PRESERVED through moderation hide/restore (hide is reversible and changes serving state\n // only; clearing it would restore an approved file with no folder and lose the steward's\n // organization).\n realmFileFolderId: z.string().min(1).optional(),\n\n // Pending-request metadata, present ONLY while `realmFileCanonStatus === 'pendingApproval'`.\n // `realmFileShareRequestId` is the CLIENT-GENERATED stable request id carried by the\n // request input; approval, decline, and withdrawal all compare against it, so a stale tab\n // can never decide a newer re-request. `realmFileShareRequestedByUid` is the recorded\n // requester — the resolution notification is addressed from THIS field, never guessed from\n // `createdByUid` (the uploader and the promoting file admin are frequently different\n // people). `realmFileShareRequestedAt` is epoch ms (ARCH-105).\n realmFileShareRequestId: z.string().min(1).optional(),\n realmFileShareRequestedByUid: z.string().min(1).optional(),\n realmFileShareRequestedAt: z.number().optional(),\n\n // Serving authority + publication gating. Additive/optional so assets\n // written before the authority build still parse: absent authorityVersion ⇒\n // 0/never-applied, absent publicationState ⇒ 'unpublished'. Every\n // serving-record mutation increments authorityVersion in an authoritative txn.\n authorityVersion: z.number().int().nonnegative().optional(),\n authorityPayloadHash: z.string().optional(),\n authoritySyncState: MediaAuthoritySyncStateSchema.optional(),\n publicationState: MediaAssetPublicationStateSchema.optional(),\n scope: MediaServingScopeSchema.optional(),\n authorityAppliedAt: z.number().optional(),\n publishedAt: z.number().optional(),\n lastAuthorityError: StructuredErrorSchema.optional(),\n // Authority-sync retry ledger (M1): the scheduled worker bumps attemptCount + sets a backoff\n // nextAttemptAt on a transient apply failure, and dead-letters (authoritySyncState='deadLetter')\n // past a threshold. Absent ⇒ 0 attempts / immediately due.\n authoritySyncAttemptCount: z.number().int().nonnegative().optional(),\n authoritySyncNextAttemptAt: z.number().optional(),\n\n // Present exactly while a retirement is owed (MediaAssetRetirementSchema).\n retirement: MediaAssetRetirementSchema.optional(),\n\n createdAt: z.number(),\n updatedAt: z.number(),\n}).strict().superRefine((val, ctx) => {\n // ===== Retirement invariant =====\n // An owed retirement already denies serving (the delete co-wrote the authority deny) and is not\n // yet retired (the drain clears it when it marks the asset retired). It owes only the asset's\n // own variants.\n if (val.retirement !== undefined) {\n if (val.servingStatus === 'servable') {\n ctx.addIssue({ code: 'custom', path: ['servingStatus'], message: 'an asset owing retirement is never servable' });\n }\n if (val.publicationState === 'retired') {\n ctx.addIssue({ code: 'custom', path: ['retirement'], message: 'a retired asset owes no retirement' });\n }\n for (const key of val.retirement.pendingVariantKeys) {\n if (!Object.prototype.hasOwnProperty.call(val.variants, key)) {\n ctx.addIssue({ code: 'custom', path: ['retirement', 'pendingVariantKeys'], message: `${key} is not one of this asset's variants` });\n }\n }\n }\n\n // ===== Realm-file legal-combination invariant =====\n // The realm-file field GROUP is one state machine, not five independently-optional\n // fields. Declared HERE, on the canonical doc schema (ARCH-102), so every writer inherits\n // it from one parse instead of each transition re-deciding — and so no reader ever has to\n // defend against a half-written state (an approved file with no folder, a pending file\n // already flipped into the realm, a decline that left request metadata behind).\n //\n // none → no realmId, no folder, no request fields\n // pendingApproval → realmId + ALL THREE request fields; NO folder (approval assigns it)\n // nonCanon|canon → realmId + folder; NO request fields (the request is resolved)\n //\n // DELIBERATELY NOT CONSTRAINED HERE: `accessTier`. Tier is origin-dependent across the\n // whole asset universe (this one schema covers every media origin, not just work files),\n // so binding tier to a realm-file status would misjudge assets that have nothing to do\n // with realms. Tier correctness on these transitions (`scoped` while pending, `artisan`\n // once approved) is enforced by the callable transactions and their tests.\n // `servingStatus` stays orthogonal too — moderation hide/quarantine is independent of a\n // file's realm standing.\n const requestFields = [\n ['realmFileShareRequestId', val.realmFileShareRequestId],\n ['realmFileShareRequestedByUid', val.realmFileShareRequestedByUid],\n ['realmFileShareRequestedAt', val.realmFileShareRequestedAt],\n ] as const;\n\n const forbid = (path: string, value: unknown, message: string) => {\n if (value !== undefined) ctx.addIssue({ code: 'custom', path: [path], message });\n };\n const require_ = (path: string, value: unknown, message: string) => {\n if (value === undefined) ctx.addIssue({ code: 'custom', path: [path], message });\n };\n\n if (val.realmFileCanonStatus === 'none') {\n forbid('realmId', val.realmId, 'realmId is only set on a realm-shared or pending asset (realmFileCanonStatus is \"none\")');\n forbid('realmFileFolderId', val.realmFileFolderId, 'realmFileFolderId is only set on an APPROVED realm-shared asset (realmFileCanonStatus is \"none\")');\n for (const [path, value] of requestFields) {\n forbid(path, value, `${path} is only set while a promotion request is pending (realmFileCanonStatus is \"none\")`);\n }\n return;\n }\n\n require_('realmId', val.realmId, `realmId is required whenever realmFileCanonStatus is \"${val.realmFileCanonStatus}\"`);\n\n if (val.realmFileCanonStatus === 'pendingApproval') {\n forbid('realmFileFolderId', val.realmFileFolderId, 'a pending request has no folder yet — the steward assigns the folder AT approval');\n for (const [path, value] of requestFields) {\n require_(path, value, `${path} is required while realmFileCanonStatus is \"pendingApproval\"`);\n }\n return;\n }\n\n // 'nonCanon' | 'canon' — approved into the pool.\n require_('realmFileFolderId', val.realmFileFolderId, `realmFileFolderId is required on an approved realm file (realmFileCanonStatus is \"${val.realmFileCanonStatus}\")`);\n for (const [path, value] of requestFields) {\n forbid(path, value, `${path} must be cleared once the request is resolved (realmFileCanonStatus is \"${val.realmFileCanonStatus}\")`);\n }\n});\nexport type MediaAsset = z.infer<typeof MediaAssetSchema>;\n\n/**\n * Where an asset stands on retirement — the one reading a caller that must not delete a record\n * naming media before its media is gone (account erasure) decides from: `absent` (no asset doc),\n * `retired` (every byte removed), `owed` (an obligation is still draining), `none` (not retiring).\n */\nexport type MediaAssetRetirementStanding = 'absent' | 'retired' | 'owed' | 'none';\n\nexport function mediaAssetRetirementStanding(\n asset: Pick<MediaAsset, 'publicationState' | 'retirement'> | undefined,\n): MediaAssetRetirementStanding {\n if (asset === undefined) return 'absent';\n if (asset.publicationState === 'retired') return 'retired';\n if (asset.retirement !== undefined) return 'owed';\n return 'none';\n}\n\n/**\n * The serving status a retirement co-write leaves on an asset: a `servable` asset is denied\n * (`hidden`) at once, and an asset already `hidden`, `quarantined`, or `deleted` keeps its status —\n * retiring an asset never downgrades a quarantine or reopens a deleted one (MEDIA-103).\n */\nexport function servingStatusOnRetirementRequest(current: MediaServingStatus): MediaServingStatus {\n return current === 'servable' ? 'hidden' : current;\n}\n\n/**\n * The serving status the drain writes when it marks an asset `retired`: `deleted`, except that a\n * `quarantined` asset stays quarantined — only the safety flows move a quarantine.\n */\nexport function servingStatusOnRetirementComplete(current: MediaServingStatus): MediaServingStatus {\n return current === 'quarantined' ? 'quarantined' : 'deleted';\n}\n"]}
1
+ {"version":3,"file":"media-assets.js","sourceRoot":"","sources":["../../src/doc-schemas/media-assets.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,2EAA2E;AAC3E,yEAAyE;AACzE,4EAA4E;AAC5E,yEAAyE;AAEzE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACtG,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,6DAA6D;AAC7D,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;AAGzF;4DAC4D;AAC5D,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;AAGjG;;uCAEuC;AACvC,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC9C,aAAa;IACb,YAAY;IACZ,mBAAmB;IACnB,aAAa;IACb,aAAa,EAAE,qEAAqE;IACpF,UAAU;IACV,mBAAmB;IACnB,oBAAoB;IACpB,UAAU;IACV,eAAe;IACf,2EAA2E;IAC3E,8CAA8C;IAC9C,kBAAkB;IAClB,wFAAwF;IACxF,wFAAwF;IACxF,qFAAqF;IACrF,wFAAwF;IACxF,mEAAmE;IACnE,WAAW;IACX,gBAAgB,EAAE,oFAAoF;IACtG,2EAA2E;IAC3E,eAAe;CAChB,CAAC,CAAC;AAGH,mCAAmC;AACnC,2EAA2E;AAC3E,4BAA4B;AAE5B;;uFAEuF;AACvF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAG1E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,MAAM;IACN,QAAQ;IACR,OAAO;IACP,2BAA2B;IAC3B,uBAAuB;CACf,CAAC;AACX,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;AAGhE;;;wEAGwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC1C,UAAU;IACV,SAAS;IACT,cAAc;IACd,oBAAoB;IACpB,iBAAiB;IACjB,eAAe;IACf,OAAO;CACR,CAAC,CAAC;AAGH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,2DAA2D;IAC5F,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,uCAAuC;IACvE,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,oDAAoD;IACjG,sBAAsB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,iEAAiE;IAC5G,6EAA6E;IAC7E,wBAAwB,EAAE,CAAC,CAAC,MAAM,EAAE;IACpC,uBAAuB,EAAE,CAAC,CAAC,MAAM,EAAE;IACnC,qDAAqD;IACrD,6FAA6F;IAC7F,2FAA2F;IAC3F,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC5C,sFAAsF;IACtF,sFAAsF;IACtF,yFAAyF;IACzF,yFAAyF;IACzF,mFAAmF;IACnF,cAAc,EAAE,CAAC,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACzE,UAAU,EAAE,qBAAqB;IACjC,oBAAoB,EAAE,yBAAyB,CAAC,QAAQ,EAAE,EAAE,sBAAsB;IAClF,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAChD,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,+DAA+D;IAC3G,gBAAgB,EAAE,yBAAyB,EAAE,qCAAqC;IAClF,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAClC,CAAC,CAAC,MAAM,EAAE,CAAC;AAGZ,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACzC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC7C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC9C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC7C;;;;;;;;;;;;;;;;;OAiBG;IACH,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC/C,CAAC,CAAC,MAAM,EAAE,CAAC;AAGZ,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,IAAI,CAAC;IAC/C,UAAU,EAAE,sDAAsD;IAClE,2BAA2B;IAC3B,WAAW;CACZ,CAAC,CAAC;AAGH,wFAAwF;AACxF,yEAAyE;AACzE,wDAAwD;AACxD,2FAA2F;AAC3F,6FAA6F;AAC7F,uFAAuF;AACvF,2FAA2F;AAC3F,iDAAiD;AACjD,6FAA6F;AAC7F,yFAAyF;AACzF,mFAAmF;AACnF,mFAAmF;AACnF,EAAE;AACF,yFAAyF;AACzF,qFAAqF;AAErF,0EAA0E;AAC1E,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAe,CAAC;AACtD;;sFAEsF;AACtF,MAAM,CAAC,MAAM,kCAAkC,GAAG,iBAA0B,CAAC;AAC7E,+DAA+D;AAC/D,MAAM,CAAC,MAAM,2BAA2B,GAAG,UAAmB,CAAC;AAC/D,mFAAmF;AACnF,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAgB,CAAC;AAExD,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC5C,sBAAsB;IACtB,kCAAkC;IAClC,2BAA2B;IAC3B,uBAAuB;CACf,CAAC;AACX,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;AAGjF;;;uFAGuF;AACvF,MAAM,CAAC,MAAM,iCAAiC,GAAG;IAC/C,2BAA2B;IAC3B,uBAAuB;CACf,CAAC;AACX,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;AAGvF;yCACyC;AACzC,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAAC,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC;AAGlG;;;kEAGkE;AAClE,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC7C,kCAAkC;IAClC,GAAG,iCAAiC;CAC5B,CAAC;AACX,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;AAGnF,sEAAsE;AAEtE,iFAAiF;AACjF,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAG1F;iFACiF;AACjF,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;AAGhG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAClE,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;QAC9B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;KACjC,CAAC,CAAC,MAAM,EAAE;IACX,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC;QACjC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAChC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;KACpC,CAAC,CAAC,MAAM,EAAE;IACX,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;QAC9B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;KACjC,CAAC,CAAC,MAAM,EAAE;IACX,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC;QAC/B,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;KACnC,CAAC,CAAC,MAAM,EAAE;CACZ,CAAC,CAAC;AAGH;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,IAAI,CAAC;IAC/C,gBAAgB;IAChB,iBAAiB;IACjB,WAAW;IACX,WAAW;IACX,wEAAwE;IACxE,8DAA8D;IAC9D,YAAY;IACZ,kBAAkB,EAAE,mJAAmJ;IACvK,eAAe;IACf,wBAAwB;IACxB,yBAAyB;IACzB,iBAAiB;IACjB,oFAAoF;IACpF,qFAAqF;IACrF,oFAAoF;IACpF,mFAAmF;IACnF,4EAA4E;IAC5E,gDAAgD;IAChD,kBAAkB;IAClB,wFAAwF;IACxF,eAAe;IACf,iBAAiB;CAClB,CAAC,CAAC;AAGH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC,MAAM,CAAC;IACxD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC1C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAChD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAE9B,aAAa,EAAE,wBAAwB;IACvC,UAAU,EAAE,qBAAqB;IAEjC,SAAS,EAAE,yBAAyB;IACpC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,KAAK,EAAE,uBAAuB,CAAC,QAAQ,EAAE;IAEzC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,uBAAuB,CAAC;IAEvD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;CACxB,CAAC,CAAC,MAAM,EAAE,CAAC;AAGZ;;;GAGG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAG7E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC;KACxC,MAAM,CAAC;IACN,8DAA8D;IAC9D,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC3C;;oBAEgB;IAChB,kBAAkB,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC5C;4EACwE;IACxE,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC7C,YAAY,EAAE,kCAAkC,CAAC,QAAQ,EAAE;IAC3D,SAAS,EAAE,qBAAqB,CAAC,QAAQ,EAAE;CAC5C,CAAC;KACD,MAAM,EAAE;KACR,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACxB,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,kBAAkB,CAAC,MAAM,EAAE,CAAC;QAC3E,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,oBAAoB,CAAC,EAAE,OAAO,EAAE,oCAAoC,EAAE,CAAC,CAAC;IAChH,CAAC;IACD,IAAI,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,GAAG,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;QACjE,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,gDAAgD,EAAE,CAAC,CAAC;IACnH,CAAC;AACH,CAAC,CAAC,CAAC;AAGL,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,SAAS,EAAE,sBAAsB;IACjC,UAAU,EAAE,gBAAgB;IAE5B,uEAAuE;IACvE,qEAAqE;IACrE,SAAS,EAAE,yBAAyB;IACpC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAE/B,gEAAgE;IAChE,UAAU,EAAE,qBAAqB;IACjC,aAAa,EAAE,wBAAwB;IAEvC,sEAAsE;IACtE,0EAA0E;IAC1E,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,uBAAuB,CAAC;IAEvD,gCAAgC;IAChC,gBAAgB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IACrD,eAAe,EAAE,0BAA0B;IAC3C,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;IACtB,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAExC,uHAAuH;IACvH,4EAA4E;IAC5E,aAAa,EAAE,0BAA0B,CAAC,QAAQ,EAAE;IAEpD,mFAAmF;IACnF,oFAAoF;IACpF,sFAAsF;IACtF,mFAAmF;IACnF,gEAAgE;IAChE,oBAAoB,EAAE,0BAA0B;IAEhD,yFAAyF;IACzF,uFAAuF;IACvF,mFAAmF;IACnF,0FAA0F;IAC1F,yFAAyF;IACzF,iBAAiB;IACjB,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAE/C,6FAA6F;IAC7F,qFAAqF;IACrF,0FAA0F;IAC1F,sFAAsF;IACtF,2FAA2F;IAC3F,qFAAqF;IACrF,+DAA+D;IAC/D,uBAAuB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACrD,4BAA4B,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC1D,yBAAyB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAEhD,uEAAuE;IACvE,0DAA0D;IAC1D,kEAAkE;IAClE,+EAA+E;IAC/E,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IAC3D,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3C,kBAAkB,EAAE,6BAA6B,CAAC,QAAQ,EAAE;IAC5D,gBAAgB,EAAE,gCAAgC,CAAC,QAAQ,EAAE;IAC7D,KAAK,EAAE,uBAAuB,CAAC,QAAQ,EAAE;IACzC,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,kBAAkB,EAAE,qBAAqB,CAAC,QAAQ,EAAE;IACpD,wFAAwF;IACxF,iGAAiG;IACjG,2DAA2D;IAC3D,yBAAyB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IACpE,0BAA0B,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAEjD,2EAA2E;IAC3E,UAAU,EAAE,0BAA0B,CAAC,QAAQ,EAAE;IAEjD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACnC,mCAAmC;IACnC,gGAAgG;IAChG,8FAA8F;IAC9F,gBAAgB;IAChB,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,GAAG,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;YACrC,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,6CAA6C,EAAE,CAAC,CAAC;QACpH,CAAC;QACD,IAAI,GAAG,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACvC,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,oCAAoC,EAAE,CAAC,CAAC;QACxG,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC;YACpD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC;gBAC7D,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,YAAY,EAAE,oBAAoB,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,sCAAsC,EAAE,CAAC,CAAC;YACtI,CAAC;QACH,CAAC;IACH,CAAC;IAED,qDAAqD;IACrD,mFAAmF;IACnF,0FAA0F;IAC1F,0FAA0F;IAC1F,uFAAuF;IACvF,gFAAgF;IAChF,EAAE;IACF,+DAA+D;IAC/D,0FAA0F;IAC1F,oFAAoF;IACpF,EAAE;IACF,uFAAuF;IACvF,yFAAyF;IACzF,uFAAuF;IACvF,wFAAwF;IACxF,2EAA2E;IAC3E,wFAAwF;IACxF,yBAAyB;IACzB,MAAM,aAAa,GAAG;QACpB,CAAC,yBAAyB,EAAE,GAAG,CAAC,uBAAuB,CAAC;QACxD,CAAC,8BAA8B,EAAE,GAAG,CAAC,4BAA4B,CAAC;QAClE,CAAC,2BAA2B,EAAE,GAAG,CAAC,yBAAyB,CAAC;KACpD,CAAC;IAEX,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,KAAc,EAAE,OAAe,EAAE,EAAE;QAC/D,IAAI,KAAK,KAAK,SAAS;YAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IACnF,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,KAAc,EAAE,OAAe,EAAE,EAAE;QACjE,IAAI,KAAK,KAAK,SAAS;YAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IACnF,CAAC,CAAC;IAEF,IAAI,GAAG,CAAC,oBAAoB,KAAK,MAAM,EAAE,CAAC;QACxC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,EAAE,yFAAyF,CAAC,CAAC;QAC1H,MAAM,CAAC,mBAAmB,EAAE,GAAG,CAAC,iBAAiB,EAAE,kGAAkG,CAAC,CAAC;QACvJ,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,oFAAoF,CAAC,CAAC;QACnH,CAAC;QACD,OAAO;IACT,CAAC;IAED,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,EAAE,yDAAyD,GAAG,CAAC,oBAAoB,GAAG,CAAC,CAAC;IAEvH,IAAI,GAAG,CAAC,oBAAoB,KAAK,iBAAiB,EAAE,CAAC;QACnD,MAAM,CAAC,mBAAmB,EAAE,GAAG,CAAC,iBAAiB,EAAE,kFAAkF,CAAC,CAAC;QACvI,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;YAC1C,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,8DAA8D,CAAC,CAAC;QAC/F,CAAC;QACD,OAAO;IACT,CAAC;IAED,iDAAiD;IACjD,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,iBAAiB,EAAE,qFAAqF,GAAG,CAAC,oBAAoB,IAAI,CAAC,CAAC;IACxK,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;QAC1C,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,2EAA2E,GAAG,CAAC,oBAAoB,IAAI,CAAC,CAAC;IACtI,CAAC;AACH,CAAC,CAAC,CAAC;AAGH,kGAAkG;AAClG,2FAA2F;AAC3F,4FAA4F;AAE5F,kFAAkF;AAClF,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,YAAY,EAAE,gBAAgB,CAAC,KAAK,CAAC,YAAY;IACjD,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC,OAAO;IACvC,SAAS,EAAE,gBAAgB,CAAC,KAAK,CAAC,SAAS;IAC3C,gBAAgB,EAAE,gBAAgB,CAAC,KAAK,CAAC,gBAAgB;IACzD,UAAU,EAAE,gBAAgB,CAAC,KAAK,CAAC,UAAU;CAC9C,CAAC,CAAC;AAGH,iEAAiE;AACjE,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3D,YAAY,EAAE,gBAAgB,CAAC,KAAK,CAAC,YAAY;IACjD,aAAa,EAAE,gBAAgB,CAAC,KAAK,CAAC,aAAa;IACnD,SAAS,EAAE,gBAAgB,CAAC,KAAK,CAAC,SAAS;CAC5C,CAAC,CAAC;AAUH,MAAM,UAAU,4BAA4B,CAC1C,KAAsE;IAEtE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACzC,IAAI,KAAK,CAAC,gBAAgB,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC3D,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAClD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gCAAgC,CAAC,OAA2B;IAC1E,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iCAAiC,CAAC,OAA2B;IAC3E,OAAO,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC","sourcesContent":["// mediaAssets/{mediaAssetId} — the canonical media registry. One doc per\n// logical uploaded media item; all variants inside. Content docs reference\n// media ONLY by `*AssetId` fields — never URLs. Serving authority is the\n// gateway Worker (cookie/grant + KV blocklist); this doc is the server-side\n// truth the edge records derive from. NO client reads, NO client writes.\n\nimport { z } from 'zod';\nimport { StructuredErrorSchema } from '@ttt-productions/edge-protocol-core';\nimport { TIMED_MEDIA_MAIN_OUTPUT_KEY, VIDEO_POSTER_OUTPUT_KEY } from '@ttt-productions/media-schemas';\nimport { FileOriginSchema } from '../media/file-origin.js';\n\n/** Who may be served the bytes (the Worker's tier check). */\nexport const MediaAccessTierSchema = z.enum(['broad', 'scoped', 'artisan', 'adminOnly']);\nexport type MediaAccessTier = z.infer<typeof MediaAccessTierSchema>;\n\n/** Canonical serving state. The edge record derives from this; hide/restore/\n * quarantine flows write it together with the blocklist. */\nexport const MediaServingStatusSchema = z.enum(['servable', 'hidden', 'quarantined', 'deleted']);\nexport type MediaServingStatus = z.infer<typeof MediaServingStatusSchema>;\n\n/** The surface that owns this asset. One asset never serves two scopes —\n * publishing to the Hall (or future realm promotion) creates a NEW asset\n * owned by the destination surface. */\nexport const MediaAssetOwnerTypeSchema = z.enum([\n 'userProfile',\n 'craftSkill',\n 'squareStreetzPost',\n 'workProject',\n 'workContent', // tale chapter / tune track / television episode media (pre-publish)\n 'hallItem',\n 'commissionListing',\n 'commissionProposal',\n 'audition',\n 'auditionEntry',\n // A file in a conversation's flat Conversation Files list (guild-invite or\n // admin-support). Chat messages own no media.\n 'conversationFile',\n // Realm shared files are the deliberate MUTATE-IN-PLACE exception: a promoted work file\n // keeps its ONE `workFile`-owned asset doc and flips tier in place, so no asset is ever\n // owned by `realmFile` today. The owner type stays declared for a future realm-owned\n // media surface; it is NOT the seam realm sharing rides (that is `realmFileCanonStatus`\n // + `realmId` + `realmFileFolderId` on the work file's own asset).\n 'realmFile',\n 'safetyEvidence', // inert system sentinel — synthetic NCII-evidence assets (never an account surface)\n // An admin video held by a system video slot; the owner id is the slot id.\n 'systemContent',\n]);\nexport type MediaAssetOwnerType = z.infer<typeof MediaAssetOwnerTypeSchema>;\n\n// ===== Media origin lineage =====\n// One canonical lineage used by media, CSAM, and safety. Embedded on every\n// media asset + every copy.\n\n/** The ONE canonical stored content media kind (what the pipeline produces and the\n * asset doc records). Distinct from the 4-value display/transport MediaTypeSchema\n * (doc-schemas/social.ts, adds 'other') and media-schemas' generic MediaKindSchema. */\nexport const ContentMediaKindSchema = z.enum(['image', 'video', 'audio']);\nexport type ContentMediaKind = z.infer<typeof ContentMediaKindSchema>;\n\n/**\n * Every variant key the media pipeline writes under an asset id — never an open `string`: the image\n * variants `TTT_MEDIA_SPECS` declares, plus the video and audio outputs, which derive from the\n * pipeline's own output-key declarations (media-schemas) — the transcoded `main` and the video's\n * `poster` frame. A copy or an ingest intent names its objects with these, so a key outside the set\n * could be written but never reclaimed. `__tests__/media-origin-lineage.test.ts` fails when a spec\n * declares a key outside the set, a pipeline output falls outside it, or a member is produced by\n * nothing.\n */\nexport const MEDIA_VARIANT_KEYS = [\n 'full',\n 'medium',\n 'small',\n TIMED_MEDIA_MAIN_OUTPUT_KEY,\n VIDEO_POSTER_OUTPUT_KEY,\n] as const;\nexport const MediaVariantKeySchema = z.enum(MEDIA_VARIANT_KEYS);\nexport type MediaVariantKey = z.infer<typeof MediaVariantKeySchema>;\n\n/** Why this asset was created from its source.\n *\n * Chat is text-only and owns no media, so no copy is ever made for a chat message. A\n * Conversation File is an ORIGINAL (`original`), never a derivative. */\nexport const MediaCopyReasonSchema = z.enum([\n 'original',\n 'variant',\n 'hall_publish',\n 'profile_derivative',\n 'moderation_copy',\n 'evidence_copy',\n 'other',\n]);\nexport type MediaCopyReason = z.infer<typeof MediaCopyReasonSchema>;\n\n/**\n * Canonical media origin lineage. A copy INHERITS\n * rootIngestId/rootAssetId/originatingUploaderUid/originatingUploadEventId/\n * originalUploadCreatedAt/rootSha256/originalSha256 unchanged; it sets its own\n * sourceAssetId/variantSha256s/copyReason/copyActorUid/currentOwner*. Client\n * requests can NEVER set or override any lineage field. Automatic account\n * action targets ONLY `originatingUploaderUid`; incident-wide blocking\n * enumerates by `rootIngestId`. Missing/corrupt lineage → safetyLocked +\n * manual decision, never a guessed ban.\n */\nexport const MediaOriginLineageV1Schema = z.object({\n lineageVersion: z.literal(1),\n rootIngestId: z.string().min(1), // first-accepted ingest event id; stable across all copies\n rootAssetId: z.string().min(1), // first asset created from that ingest\n sourceAssetId: z.string().min(1).optional(), // immediate parent copied from (absent on the root)\n originatingUploaderUid: z.string().min(1), // set server-side at first accepted ingest; IMMUTABLE; inherited\n // May be '' for a copy synthesized from a source with no known upload event.\n originatingUploadEventId: z.string(),\n originalUploadCreatedAt: z.number(),\n // '' until content hashing fills in the real sha256.\n // NOT z.string().min(1): the media pipeline does not compute a content hash at finalize yet,\n // so a freshly-ingested asset legitimately carries an empty rootSha256 until it is hashed.\n rootSha256: z.string(),\n originalSha256: z.string().min(1).optional(),\n // PARTIAL map: one hash per PRODUCED variant (an asset produces only the variants its\n // origin declares — e.g. full/medium/small for a profile picture, 'main' for others —\n // never all four). Typed keys, never an open string. `z.partialRecord` (NOT `z.record`):\n // in Zod 4 `z.record(enum,…)` is EXHAUSTIVE (demands every enum key), which would reject\n // every real asset's partial variant set; `partialRecord` makes the keys optional.\n variantSha256s: z.partialRecord(MediaVariantKeySchema, z.string().min(1)),\n copyReason: MediaCopyReasonSchema,\n createdFromOwnerType: MediaAssetOwnerTypeSchema.optional(), // never a bare string\n createdFromOwnerId: z.string().min(1).optional(),\n copyActorUid: z.string().min(1).optional(), // who triggered the copy (distinct from the original uploader)\n currentOwnerType: MediaAssetOwnerTypeSchema, // mutable; the asset's present owner\n currentOwnerId: z.string().min(1),\n}).strict();\nexport type MediaOriginLineageV1 = z.infer<typeof MediaOriginLineageV1Schema>;\n\nexport const MediaAssetVariantSchema = z.object({\n contentType: z.string().min(1),\n sizeBytes: z.number().int().nonnegative(),\n width: z.number().int().positive().optional(),\n height: z.number().int().positive().optional(),\n durationSec: z.number().positive().optional(),\n /**\n * Server-owned filename the gateway puts in `Content-Disposition` for a\n * `?download=1` request. VARIANT-specific on purpose: the downloadable bytes are\n * a PROCESSED variant, so the extension must match THIS variant's actual\n * `contentType` — a transcoded JPEG/WebP/MP4 is never labelled with the original\n * upload's incompatible extension.\n *\n * Written ONLY by backend publishers, and ONLY for surfaces that intentionally\n * offer a Download action — Work Files (including the same asset shared to a\n * Realm), Conversation Files, and downloadable Hall media. Profiles, posts,\n * covers, and every other surface leave it ABSENT so no private original filename\n * is ever exposed; an absent value serves the safe bare `attachment` disposition.\n *\n * Always produced by `normalizeDownloadFilename` (media/download-filename.ts) —\n * that helper owns the sanitization rules and the UTF-8 byte cap, so no bound is\n * restated here. A client query parameter is NEVER the filename authority.\n * Filenames are user content: never log this value.\n */\n downloadFilename: z.string().min(1).optional(),\n}).strict();\nexport type MediaAssetVariant = z.infer<typeof MediaAssetVariantSchema>;\n\nexport const MediaRetentionPolicySchema = z.enum([\n 'standard', // staging original deleted ~24h after verified mirror\n 'preserveForTrustAndSafety',\n 'legalHold',\n]);\nexport type MediaRetentionPolicy = z.infer<typeof MediaRetentionPolicySchema>;\n\n// Realm shared-files: a work file's standing in its realm's shared pool. ONE seam — the\n// approval gate is a value in this union, never a parallel status field.\n// none — not shared to the realm (default)\n// pendingApproval — a Work file admin has REQUESTED promotion; the Realm steward has not\n// decided yet. The file is NOT served to the Realm (tier stays `scoped`)\n// and is invisible to the member gallery, whose query selects only\n// nonCanon|canon — so pending rows are excluded structurally, not by a\n// filter someone can forget.\n// nonCanon — approved into the realm's pool (in a steward-chosen folder), non-canon\n// canon — approved and marked canon (realm steward toggles canon ↔ nonCanon)\n// Moderation/takedown is NOT a value here — a hidden file is hidden via the shared\n// media path (`servingStatus: 'hidden'`), so the gallery filters on servingStatus.\n//\n// Each member is declared ONCE as a named constant below and every set is assembled from\n// those names, so a member constant and the sets that contain it cannot drift apart.\n\n/** Not shared to the realm — the default standing for every work file. */\nexport const REALM_FILE_NONE_STATUS = 'none' as const;\n/** The single AWAITING-DECISION standing. Exported as a VALUE as well as a schema so a\n * consumer can branch on it (`status === REALM_FILE_PENDING_APPROVAL_STATUS`) without\n * re-quoting the member — the redeclaration guard pins this literal to this file. */\nexport const REALM_FILE_PENDING_APPROVAL_STATUS = 'pendingApproval' as const;\n/** Approved into the realm's shared pool, not marked canon. */\nexport const REALM_FILE_NON_CANON_STATUS = 'nonCanon' as const;\n/** Approved into the realm's shared pool AND marked canon by the Realm steward. */\nexport const REALM_FILE_CANON_STATUS = 'canon' as const;\n\nexport const REALM_FILE_CANON_STATUS_VALUES = [\n REALM_FILE_NONE_STATUS,\n REALM_FILE_PENDING_APPROVAL_STATUS,\n REALM_FILE_NON_CANON_STATUS,\n REALM_FILE_CANON_STATUS,\n] as const;\nexport const RealmFileCanonStatusSchema = z.enum(REALM_FILE_CANON_STATUS_VALUES);\nexport type RealmFileCanonStatus = z.infer<typeof RealmFileCanonStatusSchema>;\n\n/** The two APPROVED (steward-accepted, realm-served) standings. The member gallery query\n * and its projection accept exactly these — never the full status union, so a pending or\n * unshared row can never be typed into an approved-files response. Derived from the one\n * union above; consumers import this instead of re-quoting the members (ARCH-102). */\nexport const REALM_FILE_APPROVED_STATUS_VALUES = [\n REALM_FILE_NON_CANON_STATUS,\n REALM_FILE_CANON_STATUS,\n] as const;\nexport const RealmFileApprovedStatusSchema = z.enum(REALM_FILE_APPROVED_STATUS_VALUES);\nexport type RealmFileApprovedStatus = z.infer<typeof RealmFileApprovedStatusSchema>;\n\n/** The steward promotion-queue projection accepts only this, so an approved or unshared row\n * can never be typed into the queue. */\nexport const RealmFilePendingApprovalStatusSchema = z.literal(REALM_FILE_PENDING_APPROVAL_STATUS);\nexport type RealmFilePendingApprovalStatus = z.infer<typeof RealmFilePendingApprovalStatusSchema>;\n\n/** Every standing EXCEPT `none` — a file that has entered the request/approval lifecycle at\n * all. Derived from the two subsets above, so adding a future standing to one of them\n * reaches this set automatically. Used by the Work-side share-state projection, whose rows\n * exist only for files that are actually requested or shared. */\nexport const REALM_FILE_ACTIVE_STATUS_VALUES = [\n REALM_FILE_PENDING_APPROVAL_STATUS,\n ...REALM_FILE_APPROVED_STATUS_VALUES,\n] as const;\nexport const RealmFileActiveStatusSchema = z.enum(REALM_FILE_ACTIVE_STATUS_VALUES);\nexport type RealmFileActiveStatus = z.infer<typeof RealmFileActiveStatusSchema>;\n\n// ===== Serving authority (Durable Object) + publication gating =====\n\n/** Whether the canonical serving record has been applied to the DO authority. */\nexport const MediaAuthoritySyncStateSchema = z.enum(['pending', 'applied', 'deadLetter']);\nexport type MediaAuthoritySyncState = z.infer<typeof MediaAuthoritySyncStateSchema>;\n\n/** Whether the asset is referenced by a visible owner doc. Orthogonal to the\n * upload's processing outcome (`pendingMedia.status`) and to `servingStatus`. */\nexport const MediaAssetPublicationStateSchema = z.enum(['unpublished', 'published', 'retired']);\nexport type MediaAssetPublicationState = z.infer<typeof MediaAssetPublicationStateSchema>;\n\n/**\n * Typed immutable serving scope for scoped-tier assets. The gateway Worker matches\n * a grant against the scope by `kind` — so a whole-Work `{w}` grant matches ONLY a\n * `workProject` scope, never a conversation scope, and a conversation grant is never\n * reachable by a Work asset.\n *\n * - `workProject` — pre-publish content media (hall covers / sub-item media during\n * authoring), scoped to project read membership. Carries the matchable\n * `workProjectId`. This is the typed home for work scope on the strongly-consistent\n * serving record so the DO-fallback path can authorize it (the DO persists scope\n * only as `scopeJson`, no separate column).\n * - `workFileFolder` — a work-project FILE living in a folder. The EXACT folder scope,\n * NOT the whole-Work `{w}` grant: a custom folder's bytes must be unreachable by a\n * guildmate outside that folder's view trade-professions, so the grant callable runs\n * the per-folder view check and the Worker EXACT-matches `{workProjectId,\n * workFileFolderId}` — a plain Work `{w}` grant NEVER matches a folder file, and a\n * folder grant NEVER matches a `workProject`-scoped content asset.\n * - `guildInvite` / `adminSupport` — the two Conversation Files scopes; an EXACT\n * conversation scope, matching the `ConversationFileRef` that owns the file. There is\n * NO guildChannel scope: guild chat channels have no Conversation Files (guildmates\n * share files through Work Files, which keeps its own workFileFolder scope).\n *\n * `null` scope = no scope match required beyond the access tier (e.g. broad).\n */\nexport const MediaServingScopeSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('workProject'),\n workProjectId: z.string().min(1),\n }).strict(),\n z.object({\n kind: z.literal('workFileFolder'),\n workProjectId: z.string().min(1),\n workFileFolderId: z.string().min(1),\n }).strict(),\n z.object({\n kind: z.literal('guildInvite'),\n guildInviteId: z.string().min(1),\n }).strict(),\n z.object({\n kind: z.literal('adminSupport'),\n adminDispatchId: z.string().min(1),\n }).strict(),\n]);\nexport type MediaServingScope = z.infer<typeof MediaServingScopeSchema>;\n\n/**\n * The typed owner adapter a publication goes through. The activation job\n * carries this kind; a server-side registry validates per-kind `publicationArgs`\n * and performs the idempotent owner write. Every kind is a Firestore-owner adapter.\n */\nexport const MediaPublicationKindSchema = z.enum([\n 'profilePicture',\n 'squarePostMedia',\n 'workAsset',\n 'hallCover',\n // Realm cover — Firestore-owner adapter writes `realmCoverAssetId` onto\n // workRealms/{workRealmId} (the 'realm-cover' upload origin).\n 'realmCover',\n 'workContentMedia', // sub-item media (tale chapter / tune track / television episode) — attaches the *AssetId field on the workContent doc; scoped to the work project\n 'auditionMedia',\n 'commissionListingMedia',\n 'commissionProposalMedia',\n 'craftSkillMedia',\n // Conversation File: the ONE adapter that creates a visible Conversation File owner\n // record ({conversationParent}/conversationFiles/{conversationFileId}) and transfers\n // the quota reservation to published counters in the same transaction. Replaces the\n // removed `chatAttachment` (Channel-DO flip) and `adminSupportAttachment` (message\n // placeholder flip) kinds. Carries the typed `guildInvite` / `adminSupport`\n // MediaServingScope of the owning conversation.\n 'conversationFile',\n // Points a system video slot at its new video and retires the video it replaces, in one\n // transaction.\n 'systemVideoSlot',\n]);\nexport type MediaPublicationKind = z.infer<typeof MediaPublicationKindSchema>;\n\n/**\n * The normalized serving record — the SAME contract used to generate both\n * the DO authority row and the KV cache payload (one function, so they can't\n * drift). The R2 object key stays deterministic (`mediaAssets/{assetId}/{key}`)\n * and is NOT persisted here. `payloadHash` is computed over the canonical\n * payload EXCLUDING the hash itself (see edge-protocol-core `hashPayload`).\n */\nexport const MediaServingAuthorityRecordSchema = z.object({\n schemaVersion: z.number().int().positive(),\n assetId: z.string().min(1),\n authorityVersion: z.number().int().nonnegative(),\n operationId: z.string().min(1),\n payloadHash: z.string().min(1),\n\n servingStatus: MediaServingStatusSchema,\n accessTier: MediaAccessTierSchema,\n\n ownerType: MediaAssetOwnerTypeSchema,\n ownerId: z.string().min(1),\n scope: MediaServingScopeSchema.nullable(),\n\n variants: z.record(z.string(), MediaAssetVariantSchema),\n\n updatedAtMs: z.number(),\n}).strict();\nexport type MediaServingAuthorityRecord = z.infer<typeof MediaServingAuthorityRecordSchema>;\n\n/**\n * Why the retirement drain last deferred an owed retirement: `held` — a legal or safety hold\n * blocks the byte removal; `failed` — an edge step or an object delete failed.\n */\nexport const MediaAssetRetirementDeferralSchema = z.enum(['held', 'failed']);\nexport type MediaAssetRetirementDeferral = z.infer<typeof MediaAssetRetirementDeferralSchema>;\n\n/**\n * The retirement obligation of an asset whose owner record is gone — written onto the asset doc in\n * the SAME transaction or batch as the delete (or refusal) that leaves it unowned, together with the\n * durable authority deny when the asset is still servable (servingStatusOnRetirementRequest), so no\n * path deletes a record and then retires its media best-effort. One\n * scheduled drain owns the byte removal: it takes up due obligations in `nextAttemptAt` order, and\n * the asset becomes `retired` only when every variant's object delete has succeeded. A hold or a\n * failure defers the obligation (`attemptCount`, `nextAttemptAt`, `lastDeferral`); it is never\n * dropped. The drain deletes the obligation in the transaction that marks the asset retired.\n */\nexport const MediaAssetRetirementSchema = z\n .object({\n /** Epoch ms the obligation was co-written with the delete. */\n requestedAt: z.number().int().nonnegative(),\n /** Variant keys whose object delete has not yet succeeded (an already-absent object counts as\n * deleted). Starts as every key of `variants`; the drain removes a key only after its delete\n * succeeds. */\n pendingVariantKeys: z.array(z.string().min(1)),\n attemptCount: z.number().int().nonnegative(),\n /** Epoch ms from which the drain may take the obligation up; the drain lists due ones in this\n * order, so a deferred one rotates behind the ones that can finish. */\n nextAttemptAt: z.number().int().nonnegative(),\n lastDeferral: MediaAssetRetirementDeferralSchema.optional(),\n lastError: StructuredErrorSchema.optional(),\n })\n .strict()\n .superRefine((val, ctx) => {\n if (new Set(val.pendingVariantKeys).size !== val.pendingVariantKeys.length) {\n ctx.addIssue({ code: 'custom', path: ['pendingVariantKeys'], message: 'a variant key is owed at most once' });\n }\n if (val.lastError !== undefined && val.lastDeferral !== 'failed') {\n ctx.addIssue({ code: 'custom', path: ['lastError'], message: 'an error is recorded only for a failed attempt' });\n }\n });\nexport type MediaAssetRetirement = z.infer<typeof MediaAssetRetirementSchema>;\n\nexport const MediaAssetSchema = z.object({\n mediaAssetId: z.string().min(1),\n mediaKind: ContentMediaKindSchema,\n fileOrigin: FileOriginSchema,\n\n // Ownership / scope. ownerId is the owning doc's id within ownerType's\n // surface; scope ids let the Worker/grant layer match scoped access.\n ownerType: MediaAssetOwnerTypeSchema,\n ownerId: z.string().min(1),\n workProjectId: z.string().optional(),\n realmId: z.string().optional(),\n createdByUid: z.string().min(1),\n\n // Access + serving (canonical; edge records derive from these).\n accessTier: MediaAccessTierSchema,\n servingStatus: MediaServingStatusSchema,\n\n // Variants by key. The object key in R2 (and the Storage emulator) is\n // deterministic — mediaAssets/{mediaAssetId}/{variantKey} — never stored.\n variants: z.record(z.string(), MediaAssetVariantSchema),\n\n // Lifecycle / trust-and-safety.\n moderationStatus: z.enum(['approved', 'quarantined']),\n retentionPolicy: MediaRetentionPolicySchema,\n legalHold: z.boolean(),\n originalDeletedAt: z.number().optional(),\n\n // T&S provenance lineage. Optional so an asset that carries no lineage still parses; set server-side at first accepted\n // ingest, inherited unchanged by every copy/variant. Never client-supplied.\n originLineage: MediaOriginLineageV1Schema.optional(),\n\n // ===== Realm shared-files seam (request → steward approval → foldered pool) =====\n // A file's standing in its realm's shared pool; set at creation to 'none' and moved\n // ONLY by the realm shared-files callables. The legal combinations of this field with\n // `realmId`, `realmFileFolderId`, and the three request fields are ENFORCED by the\n // refinement below — the fields are not independently optional.\n realmFileCanonStatus: RealmFileCanonStatusSchema,\n\n // The steward-chosen folder under `workRealms/{realmId}/realmFileFolders/{id}`. There is\n // no default folder: approval IS the folder assignment, so every approved file is in a\n // folder by construction. Cleared on decline, withdrawal, and admin un-share — but\n // PRESERVED through moderation hide/restore (hide is reversible and changes serving state\n // only; clearing it would restore an approved file with no folder and lose the steward's\n // organization).\n realmFileFolderId: z.string().min(1).optional(),\n\n // Pending-request metadata, present ONLY while `realmFileCanonStatus === 'pendingApproval'`.\n // `realmFileShareRequestId` is the CLIENT-GENERATED stable request id carried by the\n // request input; approval, decline, and withdrawal all compare against it, so a stale tab\n // can never decide a newer re-request. `realmFileShareRequestedByUid` is the recorded\n // requester — the resolution notification is addressed from THIS field, never guessed from\n // `createdByUid` (the uploader and the promoting file admin are frequently different\n // people). `realmFileShareRequestedAt` is epoch ms (ARCH-105).\n realmFileShareRequestId: z.string().min(1).optional(),\n realmFileShareRequestedByUid: z.string().min(1).optional(),\n realmFileShareRequestedAt: z.number().optional(),\n\n // Serving authority + publication gating. Optional so an asset with no\n // authority state still parses: absent authorityVersion ⇒\n // 0/never-applied, absent publicationState ⇒ 'unpublished'. Every\n // serving-record mutation increments authorityVersion in an authoritative txn.\n authorityVersion: z.number().int().nonnegative().optional(),\n authorityPayloadHash: z.string().optional(),\n authoritySyncState: MediaAuthoritySyncStateSchema.optional(),\n publicationState: MediaAssetPublicationStateSchema.optional(),\n scope: MediaServingScopeSchema.optional(),\n authorityAppliedAt: z.number().optional(),\n publishedAt: z.number().optional(),\n lastAuthorityError: StructuredErrorSchema.optional(),\n // Authority-sync retry ledger: the scheduled worker bumps attemptCount + sets a backoff\n // nextAttemptAt on a transient apply failure, and dead-letters (authoritySyncState='deadLetter')\n // past a threshold. Absent ⇒ 0 attempts / immediately due.\n authoritySyncAttemptCount: z.number().int().nonnegative().optional(),\n authoritySyncNextAttemptAt: z.number().optional(),\n\n // Present exactly while a retirement is owed (MediaAssetRetirementSchema).\n retirement: MediaAssetRetirementSchema.optional(),\n\n createdAt: z.number(),\n updatedAt: z.number(),\n}).strict().superRefine((val, ctx) => {\n // ===== Retirement invariant =====\n // An owed retirement already denies serving (the delete co-wrote the authority deny) and is not\n // yet retired (the drain clears it when it marks the asset retired). It owes only the asset's\n // own variants.\n if (val.retirement !== undefined) {\n if (val.servingStatus === 'servable') {\n ctx.addIssue({ code: 'custom', path: ['servingStatus'], message: 'an asset owing retirement is never servable' });\n }\n if (val.publicationState === 'retired') {\n ctx.addIssue({ code: 'custom', path: ['retirement'], message: 'a retired asset owes no retirement' });\n }\n for (const key of val.retirement.pendingVariantKeys) {\n if (!Object.prototype.hasOwnProperty.call(val.variants, key)) {\n ctx.addIssue({ code: 'custom', path: ['retirement', 'pendingVariantKeys'], message: `${key} is not one of this asset's variants` });\n }\n }\n }\n\n // ===== Realm-file legal-combination invariant =====\n // The realm-file field GROUP is one state machine, not five independently-optional\n // fields. Declared HERE, on the canonical doc schema (ARCH-102), so every writer inherits\n // it from one parse instead of each transition re-deciding — and so no reader ever has to\n // defend against a half-written state (an approved file with no folder, a pending file\n // already flipped into the realm, a decline that left request metadata behind).\n //\n // none → no realmId, no folder, no request fields\n // pendingApproval → realmId + ALL THREE request fields; NO folder (approval assigns it)\n // nonCanon|canon → realmId + folder; NO request fields (the request is resolved)\n //\n // DELIBERATELY NOT CONSTRAINED HERE: `accessTier`. Tier is origin-dependent across the\n // whole asset universe (this one schema covers every media origin, not just work files),\n // so binding tier to a realm-file status would misjudge assets that have nothing to do\n // with realms. Tier correctness on these transitions (`scoped` while pending, `artisan`\n // once approved) is enforced by the callable transactions and their tests.\n // `servingStatus` stays orthogonal too — moderation hide/quarantine is independent of a\n // file's realm standing.\n const requestFields = [\n ['realmFileShareRequestId', val.realmFileShareRequestId],\n ['realmFileShareRequestedByUid', val.realmFileShareRequestedByUid],\n ['realmFileShareRequestedAt', val.realmFileShareRequestedAt],\n ] as const;\n\n const forbid = (path: string, value: unknown, message: string) => {\n if (value !== undefined) ctx.addIssue({ code: 'custom', path: [path], message });\n };\n const require_ = (path: string, value: unknown, message: string) => {\n if (value === undefined) ctx.addIssue({ code: 'custom', path: [path], message });\n };\n\n if (val.realmFileCanonStatus === 'none') {\n forbid('realmId', val.realmId, 'realmId is only set on a realm-shared or pending asset (realmFileCanonStatus is \"none\")');\n forbid('realmFileFolderId', val.realmFileFolderId, 'realmFileFolderId is only set on an APPROVED realm-shared asset (realmFileCanonStatus is \"none\")');\n for (const [path, value] of requestFields) {\n forbid(path, value, `${path} is only set while a promotion request is pending (realmFileCanonStatus is \"none\")`);\n }\n return;\n }\n\n require_('realmId', val.realmId, `realmId is required whenever realmFileCanonStatus is \"${val.realmFileCanonStatus}\"`);\n\n if (val.realmFileCanonStatus === 'pendingApproval') {\n forbid('realmFileFolderId', val.realmFileFolderId, 'a pending request has no folder yet — the steward assigns the folder AT approval');\n for (const [path, value] of requestFields) {\n require_(path, value, `${path} is required while realmFileCanonStatus is \"pendingApproval\"`);\n }\n return;\n }\n\n // 'nonCanon' | 'canon' — approved into the pool.\n require_('realmFileFolderId', val.realmFileFolderId, `realmFileFolderId is required on an approved realm file (realmFileCanonStatus is \"${val.realmFileCanonStatus}\")`);\n for (const [path, value] of requestFields) {\n forbid(path, value, `${path} must be cleared once the request is resolved (realmFileCanonStatus is \"${val.realmFileCanonStatus}\")`);\n }\n});\nexport type MediaAsset = z.infer<typeof MediaAssetSchema>;\n\n// Field projections of an asset doc for readers that need only a few of its fields. Each composes\n// the canonical field schemas and strips every other key, and none runs MediaAssetSchema's\n// whole-doc refinement, so a doc with unrelated stored fields still yields the fields read.\n\n/** What the Hall-media orphan reaper's asset phase reads of a candidate asset. */\nexport const MediaAssetReapCandidateSchema = z.object({\n mediaAssetId: MediaAssetSchema.shape.mediaAssetId,\n ownerId: MediaAssetSchema.shape.ownerId,\n createdAt: MediaAssetSchema.shape.createdAt,\n publicationState: MediaAssetSchema.shape.publicationState,\n retirement: MediaAssetSchema.shape.retirement,\n});\nexport type MediaAssetReapCandidate = z.infer<typeof MediaAssetReapCandidateSchema>;\n\n/** What a copy's safety-hold check reads of its source asset. */\nexport const MediaAssetCopySourceHoldFieldsSchema = z.object({\n mediaAssetId: MediaAssetSchema.shape.mediaAssetId,\n originLineage: MediaAssetSchema.shape.originLineage,\n legalHold: MediaAssetSchema.shape.legalHold,\n});\nexport type MediaAssetCopySourceHoldFields = z.infer<typeof MediaAssetCopySourceHoldFieldsSchema>;\n\n/**\n * Where an asset stands on retirement — the one reading a caller that must not delete a record\n * naming media before its media is gone (account erasure) decides from: `absent` (no asset doc),\n * `retired` (every byte removed), `owed` (an obligation is still draining), `none` (not retiring).\n */\nexport type MediaAssetRetirementStanding = 'absent' | 'retired' | 'owed' | 'none';\n\nexport function mediaAssetRetirementStanding(\n asset: Pick<MediaAsset, 'publicationState' | 'retirement'> | undefined,\n): MediaAssetRetirementStanding {\n if (asset === undefined) return 'absent';\n if (asset.publicationState === 'retired') return 'retired';\n if (asset.retirement !== undefined) return 'owed';\n return 'none';\n}\n\n/**\n * The serving status a retirement co-write leaves on an asset: a `servable` asset is denied\n * (`hidden`) at once, and an asset already `hidden`, `quarantined`, or `deleted` keeps its status —\n * retiring an asset never downgrades a quarantine or reopens a deleted one (MEDIA-103).\n */\nexport function servingStatusOnRetirementRequest(current: MediaServingStatus): MediaServingStatus {\n return current === 'servable' ? 'hidden' : current;\n}\n\n/**\n * The serving status the drain writes when it marks an asset `retired`: `deleted`, except that a\n * `quarantined` asset stays quarantined — only the safety flows move a quarantine.\n */\nexport function servingStatusOnRetirementComplete(current: MediaServingStatus): MediaServingStatus {\n return current === 'quarantined' ? 'quarantined' : 'deleted';\n}\n"]}
@@ -65,17 +65,83 @@ export declare const NotificationDeliverySchema: z.ZodObject<{
65
65
  expireAt: z.ZodOptional<z.ZodUnknown>;
66
66
  }, z.core.$strip>;
67
67
  export type NotificationDelivery = z.infer<typeof NotificationDeliverySchema>;
68
+ /**
69
+ * One phase's audience selector, keyed on `kind`, with the exact arguments that kind
70
+ * needs. A selector missing its target id or its uid list is refused rather than
71
+ * draining to zero recipients.
72
+ */
73
+ export declare const NotificationFanoutPhaseSelectorSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
74
+ kind: z.ZodLiteral<"workFollowers">;
75
+ workProjectId: z.ZodString;
76
+ }, z.core.$strip>, z.ZodObject<{
77
+ kind: z.ZodLiteral<"realmFollowers">;
78
+ workRealmId: z.ZodString;
79
+ }, z.core.$strip>, z.ZodObject<{
80
+ kind: z.ZodLiteral<"userFollowers">;
81
+ artisanUid: z.ZodString;
82
+ }, z.core.$strip>, z.ZodObject<{
83
+ kind: z.ZodLiteral<"workMembers">;
84
+ memberUids: z.ZodArray<z.ZodString>;
85
+ }, z.core.$strip>, z.ZodObject<{
86
+ kind: z.ZodLiteral<"userFollowersMany">;
87
+ memberUids: z.ZodArray<z.ZodString>;
88
+ }, z.core.$strip>, z.ZodObject<{
89
+ kind: z.ZodLiteral<"workGuildMembers">;
90
+ workProjectId: z.ZodString;
91
+ }, z.core.$strip>, z.ZodObject<{
92
+ kind: z.ZodLiteral<"realmMembers">;
93
+ workRealmId: z.ZodString;
94
+ }, z.core.$strip>, z.ZodObject<{
95
+ kind: z.ZodLiteral<"allArtisans">;
96
+ }, z.core.$strip>, z.ZodObject<{
97
+ kind: z.ZodLiteral<"allActiveUsers">;
98
+ }, z.core.$strip>, z.ZodObject<{
99
+ kind: z.ZodLiteral<"explicitUids">;
100
+ uids: z.ZodArray<z.ZodString>;
101
+ }, z.core.$strip>], "kind">;
102
+ export type NotificationFanoutPhaseSelector = z.infer<typeof NotificationFanoutPhaseSelectorSchema>;
103
+ export type NotificationFanoutPhaseSelectorKind = NotificationFanoutPhaseSelector['kind'];
104
+ /** One phase: its selector, the durable cursor in that selector's shape, and whether it is done. */
68
105
  export declare const NotificationFanoutPhaseSchema: z.ZodObject<{
69
- selector: z.ZodRecord<z.ZodString, z.ZodUnknown>;
106
+ selector: z.ZodDiscriminatedUnion<[z.ZodObject<{
107
+ kind: z.ZodLiteral<"workFollowers">;
108
+ workProjectId: z.ZodString;
109
+ }, z.core.$strip>, z.ZodObject<{
110
+ kind: z.ZodLiteral<"realmFollowers">;
111
+ workRealmId: z.ZodString;
112
+ }, z.core.$strip>, z.ZodObject<{
113
+ kind: z.ZodLiteral<"userFollowers">;
114
+ artisanUid: z.ZodString;
115
+ }, z.core.$strip>, z.ZodObject<{
116
+ kind: z.ZodLiteral<"workMembers">;
117
+ memberUids: z.ZodArray<z.ZodString>;
118
+ }, z.core.$strip>, z.ZodObject<{
119
+ kind: z.ZodLiteral<"userFollowersMany">;
120
+ memberUids: z.ZodArray<z.ZodString>;
121
+ }, z.core.$strip>, z.ZodObject<{
122
+ kind: z.ZodLiteral<"workGuildMembers">;
123
+ workProjectId: z.ZodString;
124
+ }, z.core.$strip>, z.ZodObject<{
125
+ kind: z.ZodLiteral<"realmMembers">;
126
+ workRealmId: z.ZodString;
127
+ }, z.core.$strip>, z.ZodObject<{
128
+ kind: z.ZodLiteral<"allArtisans">;
129
+ }, z.core.$strip>, z.ZodObject<{
130
+ kind: z.ZodLiteral<"allActiveUsers">;
131
+ }, z.core.$strip>, z.ZodObject<{
132
+ kind: z.ZodLiteral<"explicitUids">;
133
+ uids: z.ZodArray<z.ZodString>;
134
+ }, z.core.$strip>], "kind">;
70
135
  cursor: z.ZodNullable<z.ZodUnknown>;
71
136
  done: z.ZodBoolean;
72
137
  }, z.core.$strip>;
138
+ export type NotificationFanoutPhase = z.infer<typeof NotificationFanoutPhaseSchema>;
73
139
  /** A fanout job's priority tier, one of `NOTIFICATION_FANOUT_PRIORITIES`. */
74
140
  export declare const NotificationFanoutPrioritySchema: z.ZodLiteral<0 | 1 | 2>;
75
141
  /**
76
- * The three admin-broadcast replay fields form one provenance unit: a job has
77
- * all of them, or none. Partial state could not distinguish a safe retry from
78
- * request-id reuse and is therefore invalid.
142
+ * The stored fanout job. `phaseIndex` names an existing phase. The three
143
+ * admin-broadcast replay fields form one provenance unit: a job has all of them, or
144
+ * none, since a partial set could not distinguish a safe retry from request-id reuse.
79
145
  */
80
146
  export declare const NotificationFanoutJobSchema: z.ZodObject<{
81
147
  jobId: z.ZodString;
@@ -85,7 +151,35 @@ export declare const NotificationFanoutJobSchema: z.ZodObject<{
85
151
  priority: z.ZodLiteral<0 | 1 | 2>;
86
152
  payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
87
153
  phases: z.ZodArray<z.ZodObject<{
88
- selector: z.ZodRecord<z.ZodString, z.ZodUnknown>;
154
+ selector: z.ZodDiscriminatedUnion<[z.ZodObject<{
155
+ kind: z.ZodLiteral<"workFollowers">;
156
+ workProjectId: z.ZodString;
157
+ }, z.core.$strip>, z.ZodObject<{
158
+ kind: z.ZodLiteral<"realmFollowers">;
159
+ workRealmId: z.ZodString;
160
+ }, z.core.$strip>, z.ZodObject<{
161
+ kind: z.ZodLiteral<"userFollowers">;
162
+ artisanUid: z.ZodString;
163
+ }, z.core.$strip>, z.ZodObject<{
164
+ kind: z.ZodLiteral<"workMembers">;
165
+ memberUids: z.ZodArray<z.ZodString>;
166
+ }, z.core.$strip>, z.ZodObject<{
167
+ kind: z.ZodLiteral<"userFollowersMany">;
168
+ memberUids: z.ZodArray<z.ZodString>;
169
+ }, z.core.$strip>, z.ZodObject<{
170
+ kind: z.ZodLiteral<"workGuildMembers">;
171
+ workProjectId: z.ZodString;
172
+ }, z.core.$strip>, z.ZodObject<{
173
+ kind: z.ZodLiteral<"realmMembers">;
174
+ workRealmId: z.ZodString;
175
+ }, z.core.$strip>, z.ZodObject<{
176
+ kind: z.ZodLiteral<"allArtisans">;
177
+ }, z.core.$strip>, z.ZodObject<{
178
+ kind: z.ZodLiteral<"allActiveUsers">;
179
+ }, z.core.$strip>, z.ZodObject<{
180
+ kind: z.ZodLiteral<"explicitUids">;
181
+ uids: z.ZodArray<z.ZodString>;
182
+ }, z.core.$strip>], "kind">;
89
183
  cursor: z.ZodNullable<z.ZodUnknown>;
90
184
  done: z.ZodBoolean;
91
185
  }, z.core.$strip>>;
@@ -1 +1 @@
1
- {"version":3,"file":"notification-ledger.d.ts","sourceRoot":"","sources":["../../src/doc-schemas/notification-ledger.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB;;+CAE+C;AAC/C,eAAO,MAAM,iCAAiC;;;;iBAI5C,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,+BAA+B;;;;;EAKE,CAAC;AAC/C,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,4FAA4F;AAC5F,eAAO,MAAM,oCAAoC;;EAEE,CAAC;AACpD,MAAM,MAAM,8BAA8B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oCAAoC,CAAC,CAAC;AAKlG,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E,eAAO,MAAM,6BAA6B;;;;iBAIxC,CAAC;AAEH,6EAA6E;AAC7E,eAAO,MAAM,gCAAgC,yBAA4C,CAAC;AA4B1F;;;;GAIG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBActC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
1
+ {"version":3,"file":"notification-ledger.d.ts","sourceRoot":"","sources":["../../src/doc-schemas/notification-ledger.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB;;+CAE+C;AAC/C,eAAO,MAAM,iCAAiC;;;;iBAI5C,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,+BAA+B;;;;;EAKE,CAAC;AAC/C,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,4FAA4F;AAC5F,eAAO,MAAM,oCAAoC;;EAEE,CAAC;AACpD,MAAM,MAAM,8BAA8B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oCAAoC,CAAC,CAAC;AAKlG,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAK9E;;;;GAIG;AACH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAehD,CAAC;AACH,MAAM,MAAM,+BAA+B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qCAAqC,CAAC,CAAC;AACpG,MAAM,MAAM,mCAAmC,GAAG,+BAA+B,CAAC,MAAM,CAAC,CAAC;AAkD1F,oGAAoG;AACpG,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAcxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,6EAA6E;AAC7E,eAAO,MAAM,gCAAgC,yBAA4C,CAAC;AA4B1F;;;;GAIG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqBtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
@@ -1,7 +1,4 @@
1
- // Notification redesign ledger Firestore schemas (chat-edge-rebuild P1).
2
- // The notification-ledger field lists are FROZEN here — this file is their single
3
- // source; the durable design owner is ttt-prod docs/design/notification-system.md.
4
- // Admin-SDK-only.
1
+ // Notification delivery-ledger and fanout-job Firestore schemas. Admin-SDK-only.
5
2
  // `expireAt` is the only Firestore `Timestamp` field (native TTL); every other
6
3
  // time field is epoch-ms `number`.
7
4
  import { z } from 'zod';
@@ -67,25 +64,99 @@ export const NotificationDeliverySchema = z.object({
67
64
  });
68
65
  // ── notificationFanoutJobs/{jobId} ──────────────────────────────────────────
69
66
  // jobId = `${eventId}:${notificationType}`. No `running` state (no leases).
67
+ /**
68
+ * One phase's audience selector, keyed on `kind`, with the exact arguments that kind
69
+ * needs. A selector missing its target id or its uid list is refused rather than
70
+ * draining to zero recipients.
71
+ */
72
+ export const NotificationFanoutPhaseSelectorSchema = z.discriminatedUnion('kind', [
73
+ // Follow-edge selectors: page `followEdges` by target.
74
+ z.object({ kind: z.literal('workFollowers'), workProjectId: z.string().min(1) }),
75
+ z.object({ kind: z.literal('realmFollowers'), workRealmId: z.string().min(1) }),
76
+ z.object({ kind: z.literal('userFollowers'), artisanUid: z.string().min(1) }),
77
+ // Snapshot selectors: page a member list captured in the enqueuing transaction.
78
+ z.object({ kind: z.literal('workMembers'), memberUids: z.array(z.string()) }),
79
+ z.object({ kind: z.literal('userFollowersMany'), memberUids: z.array(z.string()) }),
80
+ // Drain-time member selectors: store only the criterion and read membership when drained.
81
+ z.object({ kind: z.literal('workGuildMembers'), workProjectId: z.string().min(1) }),
82
+ z.object({ kind: z.literal('realmMembers'), workRealmId: z.string().min(1) }),
83
+ // Projection selectors.
84
+ z.object({ kind: z.literal('allArtisans') }),
85
+ z.object({ kind: z.literal('allActiveUsers') }),
86
+ z.object({ kind: z.literal('explicitUids'), uids: z.array(z.string()) }),
87
+ ]);
88
+ /** A document-id cursor; null before the first page and once the phase is exhausted. */
89
+ const docIdCursorSchema = z.string().nullable();
90
+ /** An offset into the selector's stored uid list. */
91
+ const offsetCursorSchema = z.number().int().min(0).nullable();
92
+ /** `allArtisans` pages by `(artisanCreator, uid)`, stored as that pair JSON-encoded. */
93
+ const allArtisansCursorSchema = z
94
+ .string()
95
+ .nullable()
96
+ .refine((value) => {
97
+ if (value === null)
98
+ return true;
99
+ try {
100
+ const position = JSON.parse(value);
101
+ return (typeof position === 'object' &&
102
+ position !== null &&
103
+ typeof position.artisanCreator === 'number' &&
104
+ typeof position.uid === 'string');
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }, { message: 'allArtisans cursor must be null or JSON {artisanCreator:number, uid:string}' });
110
+ /** The cursor shape each selector kind persists. */
111
+ const NOTIFICATION_FANOUT_CURSOR_SCHEMA_BY_KIND = {
112
+ workFollowers: docIdCursorSchema,
113
+ realmFollowers: docIdCursorSchema,
114
+ userFollowers: docIdCursorSchema,
115
+ workGuildMembers: docIdCursorSchema,
116
+ allActiveUsers: docIdCursorSchema,
117
+ workMembers: offsetCursorSchema,
118
+ explicitUids: offsetCursorSchema,
119
+ userFollowersMany: z.null().or(z.object({
120
+ memberGroupIndex: z.number().int().min(0),
121
+ lastEdgeDocId: z.string().nullable(),
122
+ })),
123
+ realmMembers: z.null().or(z.object({
124
+ workId: z.string().nullable(),
125
+ workCursor: z.string().nullable(),
126
+ memberCursor: z.string().nullable(),
127
+ })),
128
+ allArtisans: allArtisansCursorSchema,
129
+ };
130
+ /** One phase: its selector, the durable cursor in that selector's shape, and whether it is done. */
70
131
  export const NotificationFanoutPhaseSchema = z.object({
71
- selector: z.record(z.string(), z.unknown()),
132
+ selector: NotificationFanoutPhaseSelectorSchema,
72
133
  cursor: z.unknown().nullable(),
73
134
  done: z.boolean(),
135
+ }).superRefine((phase, ctx) => {
136
+ const cursorSchema = NOTIFICATION_FANOUT_CURSOR_SCHEMA_BY_KIND[phase.selector.kind];
137
+ const result = cursorSchema.safeParse(phase.cursor);
138
+ if (!result.success) {
139
+ ctx.addIssue({
140
+ code: 'custom',
141
+ path: ['cursor'],
142
+ message: `cursor shape invalid for selector kind '${phase.selector.kind}': ${result.error.issues[0]?.message ?? 'wrong type'}`,
143
+ });
144
+ }
74
145
  });
75
146
  /** A fanout job's priority tier, one of `NOTIFICATION_FANOUT_PRIORITIES`. */
76
147
  export const NotificationFanoutPrioritySchema = z.literal(NOTIFICATION_FANOUT_PRIORITIES);
77
148
  const NotificationFanoutJobBaseSchema = z.object({
78
- jobId: z.string(),
149
+ jobId: z.string().min(1),
79
150
  schemaVersion: z.number(),
80
- notificationType: z.string(),
81
- eventId: z.string(),
151
+ notificationType: z.string().min(1),
152
+ eventId: z.string().min(1),
82
153
  priority: NotificationFanoutPrioritySchema,
83
154
  payload: z.record(z.string(), z.unknown()),
84
- phases: z.array(NotificationFanoutPhaseSchema),
85
- phaseIndex: z.number(),
86
- revision: z.number(),
155
+ phases: z.array(NotificationFanoutPhaseSchema).min(1),
156
+ phaseIndex: z.number().int().min(0),
157
+ revision: z.number().int().min(0),
87
158
  status: z.enum(['pending', 'complete', 'deadLetter']),
88
- attemptCount: z.number(),
159
+ attemptCount: z.number().int().min(0),
89
160
  nextAttemptAt: z.number(),
90
161
  lastError: z.string().nullable(),
91
162
  createdAt: z.number(),
@@ -100,18 +171,25 @@ const NotificationFanoutJobBaseSchema = z.object({
100
171
  payloadHash: z.string().min(1).optional(),
101
172
  });
102
173
  /**
103
- * The three admin-broadcast replay fields form one provenance unit: a job has
104
- * all of them, or none. Partial state could not distinguish a safe retry from
105
- * request-id reuse and is therefore invalid.
174
+ * The stored fanout job. `phaseIndex` names an existing phase. The three
175
+ * admin-broadcast replay fields form one provenance unit: a job has all of them, or
176
+ * none, since a partial set could not distinguish a safe retry from request-id reuse.
106
177
  */
107
178
  export const NotificationFanoutJobSchema = NotificationFanoutJobBaseSchema.superRefine((job, ctx) => {
179
+ if (job.phaseIndex >= job.phases.length) {
180
+ ctx.addIssue({
181
+ code: 'custom',
182
+ path: ['phaseIndex'],
183
+ message: `phaseIndex ${job.phaseIndex} out of range (phases.length=${job.phases.length})`,
184
+ });
185
+ }
108
186
  const fields = ['requestId', 'actorUid', 'payloadHash'];
109
187
  const present = fields.filter((field) => job[field] !== undefined);
110
188
  if (present.length !== 0 && present.length !== fields.length) {
111
189
  for (const field of fields) {
112
190
  if (job[field] === undefined) {
113
191
  ctx.addIssue({
114
- code: z.ZodIssueCode.custom,
192
+ code: 'custom',
115
193
  path: [field],
116
194
  message: 'Admin-broadcast replay provenance requires requestId, actorUid, and payloadHash together.',
117
195
  });
@@ -1 +1 @@
1
- {"version":3,"file":"notification-ledger.js","sourceRoot":"","sources":["../../src/doc-schemas/notification-ledger.ts"],"names":[],"mappings":"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","sourcesContent":["// Notification redesign ledger Firestore schemas (chat-edge-rebuild P1).\n// The notification-ledger field lists are FROZEN here — this file is their single\n// source; the durable design owner is ttt-prod docs/design/notification-system.md.\n// Admin-SDK-only.\n// `expireAt` is the only Firestore `Timestamp` field (native TTL); every other\n// time field is epoch-ms `number`.\n\nimport { z } from 'zod';\nimport type { DeliverySkipReason, DeliveryState } from '@ttt-productions/notification-core/server';\nimport { NOTIFICATION_FANOUT_PRIORITIES } from '../constants/business-platform.js';\n\n/** Native-TTL Timestamp (set only at the resolved-success terminal). */\nconst expireAtField = z.unknown().optional();\n\n/** The materialization payload carried on every delivery row.\n * `actorId` is `null` for system / no-actor occurrences (the engine writes null and\n * never injects it into `latestActorIds`). */\nexport const NotificationDeliveryPayloadSchema = z.object({\n actorId: z.string().nullable(),\n metadata: z.record(z.string(), z.unknown()),\n occurrenceAt: z.number(),\n});\n\n/**\n * A delivery row's state: notification-core's `DeliveryState`, whose ledger writes these rows.\n * `skipped` is terminal: the recipient was not eligible for a card when the row materialized (an\n * erased account), so no card was written; a re-run or a replay leaves it.\n */\nexport const NotificationDeliveryStateSchema = z.enum([\n 'queued',\n 'materialized',\n 'skipped',\n 'deadLetter',\n] as const satisfies readonly DeliveryState[]);\nexport type NotificationDeliveryState = z.infer<typeof NotificationDeliveryStateSchema>;\n\n/** Why a delivery row was skipped — notification-core's reasons, which write these rows. */\nexport const NotificationDeliverySkipReasonSchema = z.enum([\n 'recipientIneligible',\n] as const satisfies readonly DeliverySkipReason[]);\nexport type NotificationDeliverySkipReason = z.infer<typeof NotificationDeliverySkipReasonSchema>;\n\n// ── notificationDeliveries/{deliveryId} ─────────────────────────────────────\n// BOTH the recipient ledger AND the occurrence ledger. Shared-admin rows set\n// recipientUid=null (the deliveryId hash uses the literal 'shared').\nexport const NotificationDeliverySchema = z.object({\n deliveryId: z.string(),\n state: NotificationDeliveryStateSchema,\n notificationType: z.string(),\n eventId: z.string(),\n recipientUid: z.string().nullable(),\n aggregationKey: z.string(),\n strategy: z.enum(['increment', 'staticRelight']),\n payload: NotificationDeliveryPayloadSchema,\n payloadVersion: z.number(),\n materializationClass: z.enum(['directQueued', 'realtimeFallback', 'fanoutOrphan', 'retry']),\n createdAt: z.number(),\n attemptCount: z.number(),\n nextAttemptAt: z.number(),\n lastError: z.string().nullable(),\n materializedAt: z.number().nullable(),\n deadLetteredAt: z.number().nullable(),\n // Present exactly on a `skipped` row.\n skipReason: NotificationDeliverySkipReasonSchema.optional(),\n skippedAt: z.number().optional(),\n expireAt: expireAtField,\n}).superRefine((row, ctx) => {\n const skipped = row.state === 'skipped';\n if (skipped !== (row.skipReason !== undefined) || skipped !== (row.skippedAt !== undefined)) {\n ctx.addIssue({\n code: 'custom',\n path: ['state'],\n message: 'skipReason and skippedAt are present exactly on a skipped delivery row.',\n });\n }\n});\nexport type NotificationDelivery = z.infer<typeof NotificationDeliverySchema>;\n\n// ── notificationFanoutJobs/{jobId} ──────────────────────────────────────────\n// jobId = `${eventId}:${notificationType}`. No `running` state (no leases).\nexport const NotificationFanoutPhaseSchema = z.object({\n selector: z.record(z.string(), z.unknown()),\n cursor: z.unknown().nullable(),\n done: z.boolean(),\n});\n\n/** A fanout job's priority tier, one of `NOTIFICATION_FANOUT_PRIORITIES`. */\nexport const NotificationFanoutPrioritySchema = z.literal(NOTIFICATION_FANOUT_PRIORITIES);\n\nconst NotificationFanoutJobBaseSchema = z.object({\n jobId: z.string(),\n schemaVersion: z.number(),\n notificationType: z.string(),\n eventId: z.string(),\n priority: NotificationFanoutPrioritySchema,\n payload: z.record(z.string(), z.unknown()),\n phases: z.array(NotificationFanoutPhaseSchema),\n phaseIndex: z.number(),\n revision: z.number(),\n status: z.enum(['pending', 'complete', 'deadLetter']),\n attemptCount: z.number(),\n nextAttemptAt: z.number(),\n lastError: z.string().nullable(),\n createdAt: z.number(),\n updatedAt: z.number(),\n completedAt: z.number().nullable(),\n deadLetteredAt: z.number().nullable(),\n expireAt: expireAtField,\n // Admin-broadcast replay provenance. These are absent for every other fanout\n // source; together they distinguish an exact retry from request-id reuse.\n requestId: z.string().min(1).optional(),\n actorUid: z.string().min(1).optional(),\n payloadHash: z.string().min(1).optional(),\n});\n\n/**\n * The three admin-broadcast replay fields form one provenance unit: a job has\n * all of them, or none. Partial state could not distinguish a safe retry from\n * request-id reuse and is therefore invalid.\n */\nexport const NotificationFanoutJobSchema = NotificationFanoutJobBaseSchema.superRefine((job, ctx) => {\n const fields = ['requestId', 'actorUid', 'payloadHash'] as const;\n const present = fields.filter((field) => job[field] !== undefined);\n if (present.length !== 0 && present.length !== fields.length) {\n for (const field of fields) {\n if (job[field] === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: [field],\n message: 'Admin-broadcast replay provenance requires requestId, actorUid, and payloadHash together.',\n });\n }\n }\n }\n});\nexport type NotificationFanoutJob = z.infer<typeof NotificationFanoutJobSchema>;\n"]}
1
+ {"version":3,"file":"notification-ledger.js","sourceRoot":"","sources":["../../src/doc-schemas/notification-ledger.ts"],"names":[],"mappings":"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","sourcesContent":["// Notification delivery-ledger and fanout-job Firestore schemas. Admin-SDK-only.\n// `expireAt` is the only Firestore `Timestamp` field (native TTL); every other\n// time field is epoch-ms `number`.\n\nimport { z } from 'zod';\nimport type { DeliverySkipReason, DeliveryState } from '@ttt-productions/notification-core/server';\nimport { NOTIFICATION_FANOUT_PRIORITIES } from '../constants/business-platform.js';\n\n/** Native-TTL Timestamp (set only at the resolved-success terminal). */\nconst expireAtField = z.unknown().optional();\n\n/** The materialization payload carried on every delivery row.\n * `actorId` is `null` for system / no-actor occurrences (the engine writes null and\n * never injects it into `latestActorIds`). */\nexport const NotificationDeliveryPayloadSchema = z.object({\n actorId: z.string().nullable(),\n metadata: z.record(z.string(), z.unknown()),\n occurrenceAt: z.number(),\n});\n\n/**\n * A delivery row's state: notification-core's `DeliveryState`, whose ledger writes these rows.\n * `skipped` is terminal: the recipient was not eligible for a card when the row materialized (an\n * erased account), so no card was written; a re-run or a replay leaves it.\n */\nexport const NotificationDeliveryStateSchema = z.enum([\n 'queued',\n 'materialized',\n 'skipped',\n 'deadLetter',\n] as const satisfies readonly DeliveryState[]);\nexport type NotificationDeliveryState = z.infer<typeof NotificationDeliveryStateSchema>;\n\n/** Why a delivery row was skipped — notification-core's reasons, which write these rows. */\nexport const NotificationDeliverySkipReasonSchema = z.enum([\n 'recipientIneligible',\n] as const satisfies readonly DeliverySkipReason[]);\nexport type NotificationDeliverySkipReason = z.infer<typeof NotificationDeliverySkipReasonSchema>;\n\n// ── notificationDeliveries/{deliveryId} ─────────────────────────────────────\n// BOTH the recipient ledger AND the occurrence ledger. Shared-admin rows set\n// recipientUid=null (the deliveryId hash uses the literal 'shared').\nexport const NotificationDeliverySchema = z.object({\n deliveryId: z.string(),\n state: NotificationDeliveryStateSchema,\n notificationType: z.string(),\n eventId: z.string(),\n recipientUid: z.string().nullable(),\n aggregationKey: z.string(),\n strategy: z.enum(['increment', 'staticRelight']),\n payload: NotificationDeliveryPayloadSchema,\n payloadVersion: z.number(),\n materializationClass: z.enum(['directQueued', 'realtimeFallback', 'fanoutOrphan', 'retry']),\n createdAt: z.number(),\n attemptCount: z.number(),\n nextAttemptAt: z.number(),\n lastError: z.string().nullable(),\n materializedAt: z.number().nullable(),\n deadLetteredAt: z.number().nullable(),\n // Present exactly on a `skipped` row.\n skipReason: NotificationDeliverySkipReasonSchema.optional(),\n skippedAt: z.number().optional(),\n expireAt: expireAtField,\n}).superRefine((row, ctx) => {\n const skipped = row.state === 'skipped';\n if (skipped !== (row.skipReason !== undefined) || skipped !== (row.skippedAt !== undefined)) {\n ctx.addIssue({\n code: 'custom',\n path: ['state'],\n message: 'skipReason and skippedAt are present exactly on a skipped delivery row.',\n });\n }\n});\nexport type NotificationDelivery = z.infer<typeof NotificationDeliverySchema>;\n\n// ── notificationFanoutJobs/{jobId} ──────────────────────────────────────────\n// jobId = `${eventId}:${notificationType}`. No `running` state (no leases).\n\n/**\n * One phase's audience selector, keyed on `kind`, with the exact arguments that kind\n * needs. A selector missing its target id or its uid list is refused rather than\n * draining to zero recipients.\n */\nexport const NotificationFanoutPhaseSelectorSchema = z.discriminatedUnion('kind', [\n // Follow-edge selectors: page `followEdges` by target.\n z.object({ kind: z.literal('workFollowers'), workProjectId: z.string().min(1) }),\n z.object({ kind: z.literal('realmFollowers'), workRealmId: z.string().min(1) }),\n z.object({ kind: z.literal('userFollowers'), artisanUid: z.string().min(1) }),\n // Snapshot selectors: page a member list captured in the enqueuing transaction.\n z.object({ kind: z.literal('workMembers'), memberUids: z.array(z.string()) }),\n z.object({ kind: z.literal('userFollowersMany'), memberUids: z.array(z.string()) }),\n // Drain-time member selectors: store only the criterion and read membership when drained.\n z.object({ kind: z.literal('workGuildMembers'), workProjectId: z.string().min(1) }),\n z.object({ kind: z.literal('realmMembers'), workRealmId: z.string().min(1) }),\n // Projection selectors.\n z.object({ kind: z.literal('allArtisans') }),\n z.object({ kind: z.literal('allActiveUsers') }),\n z.object({ kind: z.literal('explicitUids'), uids: z.array(z.string()) }),\n]);\nexport type NotificationFanoutPhaseSelector = z.infer<typeof NotificationFanoutPhaseSelectorSchema>;\nexport type NotificationFanoutPhaseSelectorKind = NotificationFanoutPhaseSelector['kind'];\n\n/** A document-id cursor; null before the first page and once the phase is exhausted. */\nconst docIdCursorSchema = z.string().nullable();\n/** An offset into the selector's stored uid list. */\nconst offsetCursorSchema = z.number().int().min(0).nullable();\n\n/** `allArtisans` pages by `(artisanCreator, uid)`, stored as that pair JSON-encoded. */\nconst allArtisansCursorSchema = z\n .string()\n .nullable()\n .refine(\n (value) => {\n if (value === null) return true;\n try {\n const position = JSON.parse(value) as unknown;\n return (\n typeof position === 'object' &&\n position !== null &&\n typeof (position as Record<string, unknown>).artisanCreator === 'number' &&\n typeof (position as Record<string, unknown>).uid === 'string'\n );\n } catch {\n return false;\n }\n },\n { message: 'allArtisans cursor must be null or JSON {artisanCreator:number, uid:string}' },\n );\n\n/** The cursor shape each selector kind persists. */\nconst NOTIFICATION_FANOUT_CURSOR_SCHEMA_BY_KIND = {\n workFollowers: docIdCursorSchema,\n realmFollowers: docIdCursorSchema,\n userFollowers: docIdCursorSchema,\n workGuildMembers: docIdCursorSchema,\n allActiveUsers: docIdCursorSchema,\n workMembers: offsetCursorSchema,\n explicitUids: offsetCursorSchema,\n userFollowersMany: z.null().or(z.object({\n memberGroupIndex: z.number().int().min(0),\n lastEdgeDocId: z.string().nullable(),\n })),\n realmMembers: z.null().or(z.object({\n workId: z.string().nullable(),\n workCursor: z.string().nullable(),\n memberCursor: z.string().nullable(),\n })),\n allArtisans: allArtisansCursorSchema,\n} as const satisfies Record<NotificationFanoutPhaseSelectorKind, z.ZodType>;\n\n/** One phase: its selector, the durable cursor in that selector's shape, and whether it is done. */\nexport const NotificationFanoutPhaseSchema = z.object({\n selector: NotificationFanoutPhaseSelectorSchema,\n cursor: z.unknown().nullable(),\n done: z.boolean(),\n}).superRefine((phase, ctx) => {\n const cursorSchema: z.ZodType = NOTIFICATION_FANOUT_CURSOR_SCHEMA_BY_KIND[phase.selector.kind];\n const result = cursorSchema.safeParse(phase.cursor);\n if (!result.success) {\n ctx.addIssue({\n code: 'custom',\n path: ['cursor'],\n message: `cursor shape invalid for selector kind '${phase.selector.kind}': ${result.error.issues[0]?.message ?? 'wrong type'}`,\n });\n }\n});\nexport type NotificationFanoutPhase = z.infer<typeof NotificationFanoutPhaseSchema>;\n\n/** A fanout job's priority tier, one of `NOTIFICATION_FANOUT_PRIORITIES`. */\nexport const NotificationFanoutPrioritySchema = z.literal(NOTIFICATION_FANOUT_PRIORITIES);\n\nconst NotificationFanoutJobBaseSchema = z.object({\n jobId: z.string().min(1),\n schemaVersion: z.number(),\n notificationType: z.string().min(1),\n eventId: z.string().min(1),\n priority: NotificationFanoutPrioritySchema,\n payload: z.record(z.string(), z.unknown()),\n phases: z.array(NotificationFanoutPhaseSchema).min(1),\n phaseIndex: z.number().int().min(0),\n revision: z.number().int().min(0),\n status: z.enum(['pending', 'complete', 'deadLetter']),\n attemptCount: z.number().int().min(0),\n nextAttemptAt: z.number(),\n lastError: z.string().nullable(),\n createdAt: z.number(),\n updatedAt: z.number(),\n completedAt: z.number().nullable(),\n deadLetteredAt: z.number().nullable(),\n expireAt: expireAtField,\n // Admin-broadcast replay provenance. These are absent for every other fanout\n // source; together they distinguish an exact retry from request-id reuse.\n requestId: z.string().min(1).optional(),\n actorUid: z.string().min(1).optional(),\n payloadHash: z.string().min(1).optional(),\n});\n\n/**\n * The stored fanout job. `phaseIndex` names an existing phase. The three\n * admin-broadcast replay fields form one provenance unit: a job has all of them, or\n * none, since a partial set could not distinguish a safe retry from request-id reuse.\n */\nexport const NotificationFanoutJobSchema = NotificationFanoutJobBaseSchema.superRefine((job, ctx) => {\n if (job.phaseIndex >= job.phases.length) {\n ctx.addIssue({\n code: 'custom',\n path: ['phaseIndex'],\n message: `phaseIndex ${job.phaseIndex} out of range (phases.length=${job.phases.length})`,\n });\n }\n const fields = ['requestId', 'actorUid', 'payloadHash'] as const;\n const present = fields.filter((field) => job[field] !== undefined);\n if (present.length !== 0 && present.length !== fields.length) {\n for (const field of fields) {\n if (job[field] === undefined) {\n ctx.addIssue({\n code: 'custom',\n path: [field],\n message: 'Admin-broadcast replay provenance requires requestId, actorUid, and payloadHash together.',\n });\n }\n }\n }\n});\nexport type NotificationFanoutJob = z.infer<typeof NotificationFanoutJobSchema>;\n"]}
@@ -4511,7 +4511,35 @@ export declare const COLLECTION_SCHEMAS: {
4511
4511
  priority: z.ZodLiteral<0 | 1 | 2>;
4512
4512
  payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
4513
4513
  phases: z.ZodArray<z.ZodObject<{
4514
- selector: z.ZodRecord<z.ZodString, z.ZodUnknown>;
4514
+ selector: z.ZodDiscriminatedUnion<[z.ZodObject<{
4515
+ kind: z.ZodLiteral<"workFollowers">;
4516
+ workProjectId: z.ZodString;
4517
+ }, z.core.$strip>, z.ZodObject<{
4518
+ kind: z.ZodLiteral<"realmFollowers">;
4519
+ workRealmId: z.ZodString;
4520
+ }, z.core.$strip>, z.ZodObject<{
4521
+ kind: z.ZodLiteral<"userFollowers">;
4522
+ artisanUid: z.ZodString;
4523
+ }, z.core.$strip>, z.ZodObject<{
4524
+ kind: z.ZodLiteral<"workMembers">;
4525
+ memberUids: z.ZodArray<z.ZodString>;
4526
+ }, z.core.$strip>, z.ZodObject<{
4527
+ kind: z.ZodLiteral<"userFollowersMany">;
4528
+ memberUids: z.ZodArray<z.ZodString>;
4529
+ }, z.core.$strip>, z.ZodObject<{
4530
+ kind: z.ZodLiteral<"workGuildMembers">;
4531
+ workProjectId: z.ZodString;
4532
+ }, z.core.$strip>, z.ZodObject<{
4533
+ kind: z.ZodLiteral<"realmMembers">;
4534
+ workRealmId: z.ZodString;
4535
+ }, z.core.$strip>, z.ZodObject<{
4536
+ kind: z.ZodLiteral<"allArtisans">;
4537
+ }, z.core.$strip>, z.ZodObject<{
4538
+ kind: z.ZodLiteral<"allActiveUsers">;
4539
+ }, z.core.$strip>, z.ZodObject<{
4540
+ kind: z.ZodLiteral<"explicitUids">;
4541
+ uids: z.ZodArray<z.ZodString>;
4542
+ }, z.core.$strip>], "kind">;
4515
4543
  cursor: z.ZodNullable<z.ZodUnknown>;
4516
4544
  done: z.ZodBoolean;
4517
4545
  }, z.core.$strip>>;