import { Asset } from '@happyvertical/smrt-assets'; import { SmrtObjectOptions, SmrtObject } from '@happyvertical/smrt-core'; import { Fact, FactClaimSupportStatus, FactContent, FactContentRelationship, FactEvidenceStatus } from '@happyvertical/smrt-facts'; import { Image } from '@happyvertical/smrt-images'; import { AssetAssociable, MetadataAccessor } from './asset-associable'; import { ContentBodyFormat } from './body-format'; import { ContentGovernanceState, ContentReviewProfileEvaluation, CreateContentVersionOptions, IssueContentCorrectionOptions, ResolvedContentGovernance, RunContentReviewOptions } from './content-governance'; import { ContentReview } from './content-review'; import { ThumbnailOptions } from './thumbnail-generator'; type FactAuditSourceSelector = { sourceKind: string; sourceId: string; }; type FactAuditResourceRepairOptions = { sources?: FactAuditSourceSelector[]; maxFactsPerSource?: number; context?: string; }; type FactAuditClaimRecheckOptions = { claimFactIds?: string[]; sourceIds?: string[]; sources?: FactAuditSourceSelector[]; maxCandidateEvidence?: number; }; type FactEvidenceStatusUpdateOptions = { evidenceIds?: string[]; status?: FactEvidenceStatus; reason?: string; }; type FactAuditClaim = { id: string | null; fact: Record; supportStatus: FactClaimSupportStatus; claimQuote: string | null; rationale: string | null; confidence: number | null; relationship: string | null; linkMetadata: Record; evidence: Record[]; matchedFacts: Array<{ fact: Record; evidence: Record[]; }>; }; type FactAuditResourceClaim = { id: string | null; fact: Record; sourceKind: string | null; sourceId: string | null; sourceUrl: string | null; sourceTitle: string | null; locator: string | null; quote: string | null; status: FactEvidenceStatus; confidence: number | null; evidence: Record[]; }; type FactAuditState = { counts: Record; claims: FactAuditClaim[]; resourceClaims: FactAuditResourceClaim[]; warnings: string[]; generatedBy: string; latestAuditRunId: string | null; }; /** * Options for Content initialization */ export interface ContentOptions extends SmrtObjectOptions { /** * Content type classification */ type?: string | null; /** * Content variant for namespaced classification within types * Format: generator:domain:specific-type * Example: "praeco:meeting:upcoming" */ variant?: string | null; /** * Reference to file storage key */ fileKey?: string | null; /** * Author of the content */ author?: string | null; /** * Content title */ title?: string | null; /** * Short description or summary */ description?: string | null; /** * Main content body text */ body?: string | null; /** * Stored body format. */ bodyFormat?: ContentBodyFormat | null; /** * Date when content was published */ publish_date?: Date | null; /** * URL source of the content */ url?: string | null; /** * Original source identifier */ source?: string | null; /** * Publication status */ status?: 'published' | 'draft' | 'review' | 'archived' | 'deleted' | null; /** * Content state flag */ state?: 'deprecated' | 'active' | 'highlighted' | null; /** * Original URL of the content */ original_url?: string | null; /** * Content language */ language?: string | null; /** * Content tags */ tags?: string[]; /** * Hierarchical category path for URL routing * Format: 'parent/child' (e.g., 'politics/local') * Each content belongs to exactly ONE category */ category?: string | null; /** * Additional metadata */ metadata?: Record; /** * ID of the thumbnail asset for this content */ thumbnailAssetId?: string | null; /** * Transient reference IDs used by editors and API payloads. * These are synchronized into ContentReference links during save. */ referenceIds?: string[]; /** * Transient asset IDs used by editors and API payloads. */ assetIds?: string[]; /** * Tenant ID for multi-tenant isolation */ tenantId?: string | null; } /** * Structured content object with metadata and body text * * Content represents any text-based content with metadata such as * title, author, description, and publishing information. It supports * referencing related content objects. */ export declare class Content extends SmrtObject implements AssetAssociable, MetadataAccessor> { /** * Tenant ID for multi-tenant isolation * Nullable to support both tenant-scoped and global content */ tenantId: string | null; /** * Array of referenced content objects */ protected references: Content[]; /** * Content type classification */ type: string | null; /** * Content variant for namespaced classification within types * Format: generator:domain:specific-type * Example: "praeco:meeting:upcoming" */ variant: string | null; /** * Reference to file storage key */ fileKey: string | null; /** * Author of the content */ author: string | null; /** * Human-readable name for SMRT framework compatibility */ name: string; /** * Content title */ title: string; /** * Short description or summary */ description: string | null; /** * Main content body text */ body: string; /** * Format used to persist the body field. */ bodyFormat: ContentBodyFormat | null; /** * Date when content was published */ publish_date: Date | null; /** * URL source of the content */ url: string | null; /** * Original source identifier */ source: string | null; /** * Original URL of the content */ original_url: string | null; /** * Content language */ language: string | null; /** * Content tags */ tags: string[]; /** * Hierarchical category path for URL routing * Format: 'parent/child' (e.g., 'politics/local') * Each content belongs to exactly ONE category */ category: string | null; /** * Publication status */ status: 'published' | 'draft' | 'review' | 'archived' | 'deleted'; /** * Content state flag */ state: 'deprecated' | 'active' | 'highlighted'; /** * Additional JSON metadata for flexible schema extension */ metadata: Record; /** * ID of the thumbnail asset for this content */ thumbnailAssetId: string | null; /** * Creates a new Content instance */ constructor(options?: ContentOptions); /** * Initializes this content object * * @returns Promise that resolves to this instance */ initialize(): Promise; protected validateBeforeSave(): Promise; save(): Promise; private getReferenceCollection; private getFactCollection; private getFactContentCollection; private getFactSourceCollection; private getFactEvidenceCollection; private getContentVersionCollection; private getContentReviewCollection; private getContentCorrectionCollection; private getContentsCollection; private getConfiguredGovernance; resolveGovernance(): Promise; private hasPersistedGovernanceAssignments; private resolvePublicationGovernance; private requireGovernance; private requireFactLinking; private getPersistedContent; private buildReviewFingerprint; private buildTransparencySnapshot; /** * Fingerprint of the *content-bearing* publication surface only. * * This must converge: two byte-identical `save()`s of published content * have to produce the same fingerprint so the publication-version writer * (`save()`) does not append a redundant `ContentVersion` on every save. * * It therefore deliberately excludes everything that grows or carries a * timestamp/ordering with each save — `versionHistory`, `reviews`, * `corrections`, and any `generatedAt`/`createdAt`/`id` fields. The earlier * implementation fingerprinted the full transparency snapshot (which embeds * the growing `versionHistory`), so the stored fingerprint of vN predated vN * and the next save's recomputed fingerprint always differed → unbounded * redundant publication versions (#1387 blocker). * * The surface mirrors `buildReviewFingerprint`'s content block, plus the * pinned reference edges (`{ targetId, targetVersion }`) — a pin change is a * meaningful republication — and the publication profile key. */ private buildPublicationSnapshotFingerprint; private getLatestPublicationSnapshotFingerprint; private buildCorrectionDraftSnapshot; private getAssetCollection; private getContentAssetCollection; private getContentAssetLinks; private resolveAssetsForLinks; private resolveReferenceTarget; /** * Loads referenced content objects * * @returns Promise that resolves when references are loaded */ loadReferences(): Promise; private getPendingReferenceIds; private getPendingAssetIds; private syncPendingReferenceIds; private syncPendingAssetIds; /** * Adds a reference to another content object. * * @param content - Content object or URL to reference * @param options.targetVersion - Optional ContentVersion.version to pin the * citation to. Pass the target's current version (typically the latest * publication) to enable drift detection later. Pass `null` or omit to * leave the reference untracked. * @returns Promise that resolves when the reference is added */ addReference(content: Content | string, options?: { targetVersion?: number | null; }): Promise; /** * Removes a reference to another content object * * @param targetId - ID of the referenced content to remove */ removeReference(targetId: string): Promise; /** * Gets all referenced content objects * * @returns Promise resolving to an array of referenced Content objects */ getReferences(): Promise; /** * Returns the raw reference edges with their citation pins * (`{ targetId, targetVersion }`). Unlike `getReferences()` (which resolves * to `Content` objects and loses the per-edge `targetVersion`), this keeps * the pin so callers — notably version snapshots — can reconstruct pinned * citations on restore. See `ContentVersionCollection.restoreIntoContent`. */ getReferenceEdges(): Promise>; /** * Returns one entry per reference edge with the pinned `targetVersion` and * the target's latest version. Drift exists when both are present and * differ — callers can use this to surface "the source you cited has been * updated" affordances in editors or review tools. * * `currentVersion` is the target's latest **publication** `ContentVersion`, * because pins are taken against the latest publication (see `addReference`). * Auto-created `correction`/`draft`/`manual` versions bump the shared * `(content_id, version)` counter but do NOT republish, so comparing against * the max version of *any* kind produced false drift positives (#1387 #4). * * Unpinned references (`citedVersion === null`) are included with * `currentVersion` populated when available so callers can choose to * surface them as "pinnable" suggestions. */ getReferenceDrift(): Promise>; isGoverned(): boolean; getFactLinks(options?: { relationship?: FactContentRelationship; }): Promise; getFacts(options?: { relationship?: FactContentRelationship; includeSuperseded?: boolean; latestOnly?: boolean; }): Promise; addFact(fact: Fact | string, relationship?: FactContentRelationship, metadata?: Record): Promise; removeFact(factId: string, relationship?: FactContentRelationship): Promise; syncFacts(factIds: string[], relationship?: FactContentRelationship): Promise<{ added: string[]; kept: string[]; removed: string[]; }>; browseFacts(query?: string, options?: { limit?: number; offset?: number; minSimilarity?: number; includeSuperseded?: boolean; latestOnly?: boolean; }): Promise; private getFactAuditSourceMaterials; private factMatchesTenant; private findExactArticleClaimFact; private safeAuditLink; private clearGeneratedFactAudit; private clearGeneratedFactSourcesForSources; private extractReferenceFactsForAudit; private getCurrentFactAuditSupportCandidates; repairFactAudit(options?: { maxReferenceFactsPerSource?: number; maxArticleClaims?: number; context?: string; }): Promise<{ repair: { auditRunId: string; claimsExtracted: number; referenceFactsExtracted: number; warnings: string[]; }; counts: Record; claims: FactAuditClaim[]; resourceClaims: FactAuditResourceClaim[]; warnings: string[]; generatedBy: string; latestAuditRunId: string | null; }>; repairFactAuditAction(options?: { maxReferenceFactsPerSource?: number; maxArticleClaims?: number; context?: string; }): Promise<{ repair: { auditRunId: string; claimsExtracted: number; referenceFactsExtracted: number; warnings: string[]; }; counts: Record; claims: FactAuditClaim[]; resourceClaims: FactAuditResourceClaim[]; warnings: string[]; generatedBy: string; latestAuditRunId: string | null; }>; repairFactEvidence(options?: FactAuditResourceRepairOptions): Promise<{ evidenceRepair: { auditRunId: string; referenceFactsExtracted: number; repairedSources: { sourceKind: string; sourceId: string; sourceTitle: string; }[]; deletedEvidenceIds: string[]; deletedSourceIds: string[]; warnings: string[]; }; counts: Record; claims: FactAuditClaim[]; resourceClaims: FactAuditResourceClaim[]; warnings: string[]; generatedBy: string; latestAuditRunId: string | null; }>; repairFactEvidenceAction(options?: FactAuditResourceRepairOptions): Promise<{ evidenceRepair: { auditRunId: string; referenceFactsExtracted: number; repairedSources: { sourceKind: string; sourceId: string; sourceTitle: string; }[]; deletedEvidenceIds: string[]; deletedSourceIds: string[]; warnings: string[]; }; counts: Record; claims: FactAuditClaim[]; resourceClaims: FactAuditResourceClaim[]; warnings: string[]; generatedBy: string; latestAuditRunId: string | null; }>; private clearGeneratedSupportLinksForClaim; recheckFactClaims(options?: FactAuditClaimRecheckOptions): Promise<{ claimRecheck: { auditRunId: string; recheckedClaims: number; candidateFacts: number; candidateEvidence: number; warnings: string[]; }; counts: Record; claims: FactAuditClaim[]; resourceClaims: FactAuditResourceClaim[]; warnings: string[]; generatedBy: string; latestAuditRunId: string | null; }>; recheckFactClaimsAction(options?: FactAuditClaimRecheckOptions): Promise<{ claimRecheck: { auditRunId: string; recheckedClaims: number; candidateFacts: number; candidateEvidence: number; warnings: string[]; }; counts: Record; claims: FactAuditClaim[]; resourceClaims: FactAuditResourceClaim[]; warnings: string[]; generatedBy: string; latestAuditRunId: string | null; }>; updateFactEvidenceStatus(options?: FactEvidenceStatusUpdateOptions): Promise<{ evidenceStatusUpdate: { status: FactEvidenceStatus; requestedEvidenceIds: string[]; updatedEvidenceIds: string[]; skippedEvidenceIds: string[]; }; counts: Record; claims: FactAuditClaim[]; resourceClaims: FactAuditResourceClaim[]; warnings: string[]; generatedBy: string; latestAuditRunId: string | null; }>; updateFactEvidenceStatusAction(options?: FactEvidenceStatusUpdateOptions): Promise<{ evidenceStatusUpdate: { status: FactEvidenceStatus; requestedEvidenceIds: string[]; updatedEvidenceIds: string[]; skippedEvidenceIds: string[]; }; counts: Record; claims: FactAuditClaim[]; resourceClaims: FactAuditResourceClaim[]; warnings: string[]; generatedBy: string; latestAuditRunId: string | null; }>; getFactAuditState(): Promise; getFactAuditStateAction(): Promise; getFactsState(options?: { relationship?: FactContentRelationship; }): Promise<{ factIds: (string | null | undefined)[]; facts: { metadata: unknown; }[]; factLinks: { metadata: unknown; }[]; }>; syncFactsState(options?: { factIds?: string[]; relationship?: FactContentRelationship; }): Promise<{ sync: { added: string[]; kept: string[]; removed: string[]; }; factIds: (string | null | undefined)[]; facts: { metadata: unknown; }[]; factLinks: { metadata: unknown; }[]; }>; createVersion(options?: CreateContentVersionOptions): Promise; getVersions(): Promise; restoreFromVersion(versionNumber: number): Promise; getReviews(kind?: RunContentReviewOptions['kind']): Promise; listReviews(options?: { kind?: RunContentReviewOptions['kind']; }): Promise<{ findings: unknown; metadata: unknown; }[]>; getReviewRequirements(profileKey: string, governance?: ResolvedContentGovernance): Promise; getGovernanceState(): Promise; getGovernanceStateAction(): Promise; listReviewProfilesAction(): Promise; evaluateReviewProfile(profileKey: string): Promise; evaluateReviewProfileAction(options?: { profileKey?: string; }): Promise; isReadyForReviewProfile(profileKey: string): Promise; getPublishedTransparency(): Promise; getPublishedTransparencyAction(): Promise; previewTransparency(): Promise; previewTransparencyAction(): Promise; runReview(options?: RunContentReviewOptions): Promise; runReviewAction(options?: RunContentReviewOptions): Promise<{ findings: unknown; metadata: unknown; }>; reviewFacts(options?: Omit): Promise; reviewSafety(options?: Omit): Promise; getCorrections(): Promise; listCorrections(): Promise<{ metadata: unknown; }[]>; issueCorrection(options: IssueContentCorrectionOptions): Promise; issueCorrectionAction(options: IssueContentCorrectionOptions): Promise<{ metadata: unknown; }>; listVersions(): Promise<{ snapshot: unknown; metadata: unknown; }[]>; mutateVersionAction(options?: CreateContentVersionOptions & { action?: string; versionNumber?: number | string; }): Promise<{ snapshot: unknown; metadata: unknown; } | { referenceIds: unknown[]; references: { [x: string]: unknown; }[]; assetIds: unknown[]; assets: { [x: string]: unknown; }[]; }>; /** * Note: toJSON() is inherited from SmrtObject * * The parent implementation handles: * - STI discriminator (_meta_type) for polymorphic queries * - Meta field extraction (_meta_data) for child-specific fields * - Automatic serialization of all fields from manifest * * DO NOT override toJSON() unless you call super.toJSON() first. * See issue #377 for details on why this override was removed. */ /** * Get category segments as array * @example 'politics/local' -> ['politics', 'local'] */ getCategorySegments(): string[]; /** * Get parent category path * @example 'politics/local/town' -> 'politics/local' * @example 'politics' -> null */ getParentCategory(): string | null; /** * Get root (top-level) category * @example 'politics/local/town' -> 'politics' */ getRootCategory(): string | null; /** * Get all ancestor category paths (for breadcrumbs) * @example 'politics/local' -> ['politics', 'politics/local'] */ getAncestorPaths(): string[]; /** * Check if content belongs to a category (optionally including subcategories) * @param categoryPath - Category to check * @param includeChildren - If true, matches 'politics' for content in 'politics/local' */ isInCategory(categoryPath: string, includeChildren?: boolean): boolean; /** * Get all assets associated with this content * @param relationship - Optional filter by relationship type (e.g., 'thumbnail', 'attachment') * @returns Promise resolving to array of assets */ getAssets(relationship?: string): Promise; /** * Add an asset to this content with a relationship type * @param asset - The asset to associate * @param relationship - Relationship type (e.g., 'thumbnail', 'attachment', 'inline') * @param sortOrder - Optional sort order for display */ addAsset(asset: Asset, relationship?: string, sortOrder?: number): Promise; /** * Remove an asset from this content * @param assetId - ID of the asset to remove * @param relationship - Optional specific relationship to remove (removes all if not specified) */ removeAsset(assetId: string, relationship?: string): Promise; /** * Get the full metadata record. Always returns a plain object — never * `null`, never an array — so callers can safely read nested keys without * defensive checks. * * Pure read with no side-effect on `this.metadata`: if the field is * currently `null` (e.g. fresh from the DB) or non-record-shaped, an * empty object is returned but the field is **not** mutated. This avoids * accidentally marking the object dirty during a read, which would * otherwise cause SmrtObject's save lifecycle to write `{}` back over a * NULL column on the next save. Callers that want to normalise the * stored field should use {@link Content.setMetadata}. */ getMetadata(): Record; /** * Replace the full metadata record. Passing `null`/`undefined` (or any * non-record value such as an array) clears it to an empty object so * downstream readers can rely on the field always being a plain object. */ setMetadata(metadata: Record | null | undefined): void; /** * Shallow-merge a patch over the current metadata. Returns the resulting * record so callers can chain reads without re-reading the field. Unlike * {@link Content.getMetadata}, this method does intentionally write back * to `this.metadata` because the merge is a write. */ updateMetadata(patch: Partial>): Record; /** * Get the thumbnail image for this content * @returns Promise resolving to the thumbnail Image or null */ getThumbnail(): Promise; /** * Set the thumbnail image for this content * @param image - The image to set as thumbnail */ setThumbnail(image: Image): Promise; /** * Generate a thumbnail for this content using the specified strategy * * @param options - Thumbnail generation options including strategy * @returns Promise resolving to the generated Image * * @example Headline card thumbnail * ```typescript * const thumbnail = await content.generateThumbnail({ * strategy: 'headline-card', * brandColor: '#1a56db', * logoUrl: 'https://example.com/logo.png' * }); * ``` * * @example Static map thumbnail (requires metadata.latitude/longitude) * ```typescript * const thumbnail = await content.generateThumbnail({ * strategy: 'static-map', * mapProvider: 'mapbox' * }); * ``` * * @example AI-generated thumbnail * ```typescript * const thumbnail = await content.generateThumbnail({ * strategy: 'ai-generate' * }); * ``` */ generateThumbnail(options: ThumbnailOptions): Promise; } export {}; //# sourceMappingURL=content.d.ts.map