Skip to content

Conversation

@Julusian
Copy link
Member

@Julusian Julusian commented Jan 12, 2026

About the Contributor

This pull request is posted on behalf of the BBC

Type of Contribution

This is a: Feature

Current Behavior

New Behavior

This is a continuation of #1589
Closes #1154

This is the third stage of reimplementing #1258. I believe we do not have any plans to complete the final steps at the moment.

This continues the previous PR and reworks the package generation flow so that pieceInstances add a claim to each expectedPackage they reference. With this, ingest will not delete these packages, and it means that the pieceInstances will share the statuses of the existing packages.
This will both reduce the number of packages and statuses in the database, and minimises the amount of packages that get pushed to package manager at runtime which need to be resolved ASAP.

With this, when adlibbing a piece, we should never see temporary zebras on the adlibbed piece while waiting for package-manager to catchup

The benefit of this is that the pieceInstances instantly have package statuses available.
With a hack in place to stop pieceInstances from using the statuses of their underlying pieces, this causes the time for the ui to show states to decrease from ~5s to instantly.
While falling back to the piece has been sufficient in many cases, it left pieceinstances created from adlib-actions to suffer.

Note: No treatment has been done for buckets yet, due to the different ownership structures, it needs some further thought on how to handle those correctly.

Testing

  • I have added one or more unit tests for this PR
  • I have updated the relevant unit tests
  • No unit test changes are needed for this PR

Affected areas

Time Frame

Other Information

Status

  • PR is ready to be reviewed.
  • The functionality has been tested by the author.
  • Relevant unit tests has been added / updated.
  • Relevant documentation (code comments, system documentation) has been added / updated.

Summary

Reworks ExpectedPackages generation and management to support shared ownership between ingest and playout subsystems. This fundamental restructuring aims to reduce database load, improve performance of package status availability for UI clients, and prevent erroneous package deletion when parts are set as next.

Key Changes

Data Model Restructuring

ExpectedPackageDB Interface: Replaced a discriminated union of package types with a single public interface that uses rundownId and bucketId to distinguish ownership:

  • Added fields: package (ReadonlyDeep<Omit<ExpectedPackage.Base>>), ingestSources (array of source descriptors), playoutSources (with pieceInstanceIds array)
  • Removed old union variants (ExpectedPackageFromRundown, ExpectedPackageDBFromPiece, etc.)
  • Updated _id generation via new getExpectedPackageId function that hashes package content with parent ownership context

New Source Types: Introduced comprehensive ingest source abstractions:

  • ExpectedPackageIngestSource union type covering all source variants (piece, adlib action, baseline, bucket, studio baseline)
  • Source objects now carry: fromPieceType, blueprintPackageId, listenToPackageInfoUpdates, and source-specific ownership fields
  • Added IngestExpectedPackage<TPackageSource> wrapper type used throughout ingestion pipelines

PieceInstance Enhancement: Added optional neededExpectedPackageIds?: ExpectedPackageId[] field to track which packages a piece instance claims.

Package Ownership and Sharing

Dual Ownership Model: Packages now track both ingest and playout claims:

  • ingestSources[]: Who created the package (ingest, baseline, studio setup)
  • playoutSources.pieceInstanceIds[]: Which piece instances are using the package
  • Packages persist as long as either ownership chain exists

Cleanup Strategy: New handleCleanupOrphanedExpectedPackageReferences function reconciles package references:

  • Identifies piece instances that are no longer valid (reset or deleted)
  • Removes stale pieceInstanceIds from playoutSources
  • Deletes packages only when all ingest sources are gone and no playout references remain
  • Runs as debounced studio job to avoid excessive database churn

Generation and Publication Flows

Ingest-side Changes:

  • Renamed updateExpectedPackagesForPartModelupdateExpectedMediaAndPlayoutItemsForPartModel
  • Introduced IngestExpectedPackage wrapper to separate ingest model from database representation
  • Added ExpectedPackageCollector utility to deduplicate packages by composite key (package ID, blueprint package ID, listenToPackageInfoUpdates flag)
  • New writeExpectedPackagesChangesForRundown function handles bulk insert/update/delete with smart source merging

Playout-side Changes:

  • New writeExpectedPackagesForPlayoutSources function registers piece instances as claims on existing packages
  • Piece instances now track neededExpectedPackageIds for efficient ownership tracking
  • Studio baseline packages stored as ingest sources rather than separate document type

Package Manager Publication:

  • PieceInstanceCompact type changed from exposing nested piece.expectedPackages to exposing partInstanceId and neededExpectedPackageIds
  • ExpectedPackageDBCompact type exposes only _id and package fields (minimal surface for UI)
  • Publication regeneration simplified to focus on ExpectedPackageIds rather than PieceInstanceIds
  • Priority calculation moved to post-processing phase via updatePackagePriorities

Database Optimization

Collection Indexing: Updated indexes on ExpectedPackages:

  • Removed: fromPieceType, pieceId indexes
  • Added: studioId + bucketId index
  • Retained: studioId + rundownId and rundownId indexes

Migration Path: New migration step (X_X_X.ts) converts old package format to new structure:

  • Maps pre-R53 package types to new ingest source structure
  • Preserves _id but regenerates based on new hash
  • Converts single ownership to array-based ingestSources
  • Initializes empty playoutSources

Job Queue Infrastructure

Worker Job Queue Manager: Introduced new WorkerJobQueueManager class for in-process job queueing:

  • Replaces inline queue implementation with external manager
  • Supports priority queues, debounce deduplication, and long-polling workers
  • Tracks per-queue metrics (total, success, error counts)
  • Handles worker wakeup via Meteor.defer

New Studio Job: CleanupOrphanedExpectedPackageReferences job queued after playout updates to reconcile package ownership.

Hash Function Enhancement

Updated hashObj function to handle null/undefined consistently:

  • Arrays preserve order and undefined values via "index:hash(element)" format
  • Objects skip undefined properties (treat as equivalent to empty objects)
  • Enables stable ID generation for package deduplication

Supporting Infrastructure

Mock/Test Enhancements:

  • Added comprehensive test coverage for job queue manager, cleanup logic, and watched packages
  • Enhanced mock collection support for bulk operations (insertOne, deleteOne)
  • New test utilities for constructing mock expected package hierarchies

Type Safety: Replaced loose typing with strict source and ownership tracking; eliminated reliance on PieceInstanceId as primary package identifier.

Performance Impact

  • Database: Fewer document writes (packages shared vs. duplicated), consolidated bulk operations
  • UI Latency: Package statuses now available immediately upon piece instance creation (previously ~5s delay)
  • Package Manager: Fewer package documents pushed at runtime due to sharing between ingest and playout

Breaking Changes

  • ExpectedPackageDB interface shape completely changed (single interface vs. discriminated union)
  • Public functions renamed: updateExpectedPackagesForPartModel, updateExpectedPackagesForRundownBaseline
  • Removed IngestJobs.ExpectedPackagesRegenerate job type
  • Package Manager publication surface changed for PieceInstance and ExpectedPackage types

Outstanding Work

  • Bucket ownership handling deferred due to different ownership semantics requiring further design
  • Some test checklist items marked but not checked

@Julusian Julusian requested a review from a team as a code owner January 12, 2026 13:32
@Julusian Julusian added the Contribution from BBC Contributions sponsored by BBC (bbc.co.uk) label Jan 12, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

Walkthrough

This pull request performs a comprehensive restructuring of the ExpectedPackages system, migrating from a piece-centric to an ingest-source-centric data model, introducing a new in-process job queue manager for workers, and refactoring how packages are generated, tracked, and prioritized across ingest and playout workflows.

Changes

Cohort / File(s) Summary
ExpectedPackages Core Data Model
packages/corelib/src/dataModel/ExpectedPackages.ts, packages/corelib/src/dataModel/Old/ExpectedPackagesR52.ts
Replaced union type structure with single ExpectedPackageDB interface using rundownId and bucketId for ownership. Introduced comprehensive ingest source types (ExpectedPackageIngestSourcePiece, ExpectedPackageIngestSourceAdlibAction, etc.). Changed getExpectedPackageId signature to compute stable IDs from package hash and parent ID. Added isPackageReferencedByPlayout helper. Created legacy snapshot types in ExpectedPackagesR52.
Worker Job Queue System
meteor/server/worker/jobQueue.ts, meteor/server/worker/jobQueue.test.ts, meteor/server/worker/worker.ts
Introduced new WorkerJobQueueManager class managing per-queue high/low priority job state with debounce, deduplication, and completion handling. Added comprehensive test suite covering queuing, debounce, priority handling, and job lifecycle. Refactored worker.ts to delegate queue logic to new manager, replacing in-file implementations.
Ingest Package Generation
packages/job-worker/src/ingest/expectedPackages.ts, packages/job-worker/src/ingest/model/IngestExpectedPackage.ts, packages/job-worker/src/ingest/model/IngestPartModel.ts, packages/job-worker/src/ingest/model/IngestModel.ts
Refactored package generation from per-piece/adlib to bucket-oriented flow. Renamed updateExpectedPackagesForPartModel to updateExpectedMediaAndPlayoutItemsForPartModel. Introduced IngestExpectedPackage wrapper and ExpectedPackageCollector for per-source deduplication. Updated IngestModel public API to use new ingest source types and added IngestDatabasePersistedModel.
Ingest Model Implementation
packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts, packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts, packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts, packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts, packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts, packages/job-worker/src/ingest/model/implementation/LoadIngestModel.ts
Changed ExpectedPackagesStore generic to use ingest source constraint instead of concrete DB types. Replaced per-store change tracking with boolean flag. Updated SaveIngestModelHelper constructor to accept rundownId and added new writeExpectedPackagesChangesForRundown function for bulk write coordination. Modified setRundownBaseline to accept expected packages parameter.
Baseline Package Generation
packages/job-worker/src/ingest/generationRundown.ts
Introduced generateExpectedPackagesForBaseline function to collect baseline packages. Updated call to updateExpectedMediaAndPlayoutItemsForRundownBaseline. Changed source references from pkg.fromPieceType to pkg.source.fromPieceType.
Segment Generation Updates
packages/job-worker/src/ingest/generationSegment.ts, packages/job-worker/src/ingest/bucket/bucketAdlibs.ts, packages/job-worker/src/ingest/bucket/import.ts, packages/job-worker/src/ingest/packageInfo.ts, packages/job-worker/src/ingest/commit.ts
Updated segment generation to use renamed function and new source-based filtering. Modified bucket adlib processing to consolidate cleanup paths and pass bucketId. Refactored packageInfo.ts to iterate per-ingest-source via findExpectedPackageIngestSources. Updated CommitIngestOperation to use IngestDatabasePersistedModel.
Playout Expected Packages
packages/job-worker/src/playout/expectedPackages.ts, packages/job-worker/src/playout/__tests__/expectedPackages.test.ts
Added new handleCleanupOrphanedExpectedPackageReferences function to reconcile packages with valid piece instances, removing stale references and packages. Included comprehensive test suite validating cleanup behavior across scenarios.
Playout Model and Save Path
packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts, packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts, packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts, packages/job-worker/src/playout/model/implementation/SavePlayoutModel.spec.ts
Integrated per-rundown expected package write path via writeExpectedPackagesForPlayoutSources. Updated PlayoutPieceInstanceModelImpl to track updatedExpectedPackages and populate neededExpectedPackageIds. Changed setExpectedPackagesForStudioBaseline to accept ExpectedPackage.Any[]. Added extensive test coverage for playout source updates.
Studio Baseline Helpers
packages/job-worker/src/studio/model/StudioBaselineHelper.ts, packages/job-worker/src/studio/model/StudioPlayoutModel.ts, packages/job-worker/src/studio/model/StudioPlayoutModelImpl.ts
Replaced array-based pending packages with Map<ExpectedPackageId, IngestExpectedPackage>. Updated setExpectedPackages to accept ExpectedPackage.Any[] and sanitize with deduplication. Adjusted save path to construct proper ExpectedPackageDB documents with ingest/playout sources.
Package Manager Publication
meteor/server/publications/packageManager/expectedPackages/contentCache.ts, meteor/server/publications/packageManager/expectedPackages/generate.ts
Changed PieceInstanceCompact to expose partInstanceId and neededExpectedPackageIds instead of nested expectedPackages. Introduced ExpectedPackageDBCompact type. Updated generation logic to work with nested package.sources and compute IDs from expected package _id and deviceId. Removed pieceInstanceId from document key generation.
Package Manager Priority
meteor/server/publications/packageManager/expectedPackages/publication.ts
Removed regeneration of piece instances. Added updatePackagePriorities post-processing step that maps priorities based on active part and piece instances. Introduced priority tier constants (PLAYOUT_CURRENT, PLAYOUT_NEXT, OTHER). Simplified update flow to regenerate packages then correct priorities.
Piece Content Status UI
meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts, meteor/server/publications/pieceContentStatusUI/__tests__/checkPieceContentStatus.test.ts, meteor/server/publications/pieceContentStatusUI/bucket/regenerateForItem.ts, meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts
Introduced packageOwnerId parameter (RundownId \| BucketId \| StudioId) to decouple package lookup from piece references. Replaced multi-ID approach with single getExpectedPackageId call. Updated test to pass RundownId as ownership context. Propagated rundownId or bucketId through regeneration functions.
Ingest-related Cleanup
meteor/server/api/ingest/debug.ts, meteor/server/api/ingest/packageInfo.ts
Removed debug method debug_recreateExpectedPackages and related DB imports. Refactored packageInfo.ts to iterate per-ingest-source with per-source debounce routing. Changed debounce functions to accept generic ExpectedPackageDB with source parameter.
Collection Indexing
meteor/server/collections/packages-media.ts
Replaced studioId + pieceId index with studioId + rundownId. Removed fromPieceType index. Added studioId + bucketId index. Aligned indexing with new ownership model.
Migration Scripts
meteor/server/migration/1_50_0.ts, meteor/server/migration/X_X_X.ts
Updated migration to reference old package types from ExpectedPackagesR52 snapshot. Added new migration step "convert ExpectedPackages to new format" that rewrites old schema to new ingest-source structure, preserving _id and remapping rundownId/bucketId.
Test Updates
meteor/server/api/__tests__/cleanup.test.ts, packages/corelib/src/__tests__/hash.spec.ts, packages/job-worker/src/blueprints/context/__tests__/watchedPackages.test.ts, packages/job-worker/src/ingest/__tests__/expectedPackages.test.ts, packages/job-worker/src/ingest/model/implementation/__tests__/SaveIngestModel.spec.ts, packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts
Updated test data structures, added comprehensive test suites for new job queue manager, watched packages helper, hash function, and save paths. Introduced mock helpers for expected packages and ingest data.
Auxiliary Updates
packages/corelib/src/dataModel/PieceInstance.ts, packages/corelib/src/dataModel/ExpectedPackageWorkStatuses.ts, packages/corelib/src/dataModel/PackageInfos.ts, packages/corelib/src/hash.ts, packages/corelib/src/mongo.ts, packages/corelib/src/pubsub.ts, packages/corelib/src/snapshots.ts, packages/corelib/src/worker/ingest.ts, packages/corelib/src/worker/studio.ts, packages/meteor-lib/src/collections/ExpectedPackages.ts, packages/shared-lib/src/package-manager/publications.ts, packages/job-worker/src/blueprints/postProcess.ts, packages/job-worker/src/blueprints/context/watchedPackages.ts, packages/job-worker/src/playout/snapshot.ts, packages/job-worker/src/playout/timeline/generate.ts, packages/job-worker/src/playout/adlibAction.ts, packages/job-worker/src/ipc.ts, packages/job-worker/src/jobs/index.ts, packages/job-worker/src/manager.ts, packages/job-worker/src/workers/context/JobContextImpl.ts, packages/job-worker/src/workers/context/util.ts, packages/job-worker/src/workers/ingest/jobs.ts, packages/job-worker/src/workers/studio/jobs.ts, packages/webui/src/client/ui/Settings/DevicePackageManagerSettings.tsx, packages/webui/src/client/ui/Status/package-status/PackageStatus.tsx, packages/webui/src/client/ui/Status/package-status/index.tsx
Added neededExpectedPackageIds to PieceInstance. Updated work status types to use public IDs. Added QueueJobOptions interface for job queue configuration. Introduced CleanupOrphanedExpectedPackageReferences studio job. Removed ExpectedPackagesRegenerate ingest job. Updated hash function with explicit null/undefined/array handling. Extended Mongo operators for $elemMatch and $in. Enhanced UI reload tracking with per-device race guards.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 A grand restructuring done with care,
From pieces to sources everywhere,
Job queues aligned, priorities bright,
Ownership clear—rundowns and buckets just right!
Packages now flow through a brand new design,
The ingest and playout dance in fine line! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.72% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive While most changes align with ExpectedPackages management, numerous collateral updates across worker jobs, package handling, and UI components may extend beyond core objectives, warranting clarification on scope boundaries. Verify that all worker job updates (CleanupOrphanedExpectedPackageReferences), UI changes, and auxiliary modifications are intentional dependencies of the core refactoring or can be deferred.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the main change: reworking ExpectedPackages generation/management to share packages between ingest and playout.
Linked Issues check ✅ Passed The code changes comprehensively implement the objectives from issue #1154 by refactoring ExpectedPackages to support shared ownership between ingest and playout, enabling pieceInstances to reference and share package statuses without ingest deletion.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nytamin nytamin self-requested a review January 12, 2026 14:11
This is a preparation step for the documents having multiple owners. In the current iteration, each document will remain with one owner.
…nces as owners to existing docs

# Conflicts:
#	meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts
#	packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts
@Julusian Julusian force-pushed the upstream/expected-packages-shared-ingest-and-playout branch from b3ba362 to 03346be Compare January 13, 2026 10:38
Copy link
Member

@nytamin nytamin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not able to run a full test at this point, but the concepts and implementations look solid in my opinion.
I left a few very minor notes.

@@ -0,0 +1,137 @@
import type { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these types are only ever used during migration, should we move this file to under meteor\server\migration\ ?

plannedStartedPlayback?: Time
plannedStoppedPlayback?: Time

neededExpectedPackageIds?: ExpectedPackageId[]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment describing this property and its intended use?


export function setDefaultIdOnExpectedPackages(expectedPackages: ExpectedPackage.Any[] | undefined): void {
export function sanitiseExpectedPackages(expectedPackages: ExpectedPackage.Any[] | undefined): void {
// Fill in ids of unnamed expectedPackage
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Fill in ids of unnamed expectedPackage

This comment is no longer valid

@nytamin
Copy link
Member

nytamin commented Jan 14, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@meteor/server/worker/jobQueue.ts`:
- Line 196: Update the inline comment in jobQueue.ts that currently reads "So we
psuh a `null` job at the start so that it interrupts immediately" to correct the
typo to "push" (e.g., "So we push a `null` job at the start so that it
interrupts immediately") near the code that enqueues the null job in the job
queue processing logic.

In `@packages/corelib/src/hash.ts`:
- Around line 14-18: The Array branch in the Array.isArray(obj) block
double-hashes elements by calling hashObj on the `"index:hash"` string; change
the mapping in that block so it produces the string `${i}:${hashObj(val)}` (like
the object branch) and then call getHash on strs.join('|') once, ensuring order
and undefined preservation; update the mapping that defines strs and leave
getHash(strs.join('|')) as the final hash call (referencing hashObj, strs,
getHash).

In
`@packages/job-worker/src/blueprints/context/__tests__/watchedPackages.test.ts`:
- Around line 207-256: The test name/assertion mismatch: update the test
description to reflect actual behavior (the test currently asserts that the
package is found and package info is returned), e.g. change the it(...) string
to "returns package info for packages with listenToPackageInfoUpdates: false"
(or similar), keeping the existing assertions that call
WatchedPackagesHelper.create, hasPackage(packageId) and
getPackageInfo('package1'); alternatively, if you intended to assert that no
package info is returned, change the expectations to
expect(helper.hasPackage(packageId)).toBe(true) and
expect(helper.getPackageInfo('package1')).toHaveLength(0) and adjust setup
accordingly, referencing the test case around WatchedPackagesHelper.create,
hasPackage, and getPackageInfo.

In `@packages/shared-lib/src/package-manager/package.ts`:
- Around line 43-44: Update the _id JSDoc to explicitly state its parent
scoping: change the comment for _id (ExpectedPackageId) to indicate it is a
unique identifier scoped to the parent context (rundown, studio, or bucket) for
which the package is defined, referencing the getExpectedPackageId(parentId)
behavior so integrators know scoping affects deduplication and matching across
ingest/playout.

In `@packages/webui/src/client/ui/Settings/DevicePackageManagerSettings.tsx`:
- Around line 59-61: The finally block unconditionally clears
reloadingNow.current which can race with concurrent requests; change the finally
to only set reloadingNow.current = null if it still equals the deviceId for that
request (i.e., in the finally check if reloadingNow.current === deviceId then
set to null) so a later-started request doesn’t get invalidated by an earlier
completion; locate the finally that currently does reloadingNow.current = null
and add this conditional guard around that assignment.

In `@packages/webui/src/client/ui/Status/package-status/index.tsx`:
- Line 149: Remove the debug console.log calls in the package-status module:
delete the console.log('p', p) call (and the other console.log found later in
the same component) so no debug logging is emitted from that component in
production; after removing them, run lint/format and a dev build/test to ensure
no leftover debug statements or unused variables remain.
♻️ Duplicate comments (3)
packages/corelib/src/dataModel/PieceInstance.ts (1)

78-79: Add documentation for this property.

This field is central to the PR's goal of sharing packages between ingest and playout. A comment describing its purpose would help future maintainers understand when and how this array is populated. As noted in a previous review, please add a comment describing this property and its intended use.

📝 Suggested documentation
 	plannedStoppedPlayback?: Time

+	/**
+	 * The ExpectedPackage IDs that this PieceInstance references.
+	 * Used to track which packages are needed for playout, allowing
+	 * pieceInstances to share package statuses with ingest sources.
+	 */
 	neededExpectedPackageIds?: ExpectedPackageId[]
packages/job-worker/src/ingest/expectedPackages.ts (1)

275-282: Stale comment - function behavior changed.

The comment on line 276 says "Fill in ids of unnamed expectedPackage" but the function now sets contentVersionHash instead. This was flagged in a past review. Update the comment to accurately describe the current behavior.

📝 Suggested fix
 export function sanitiseExpectedPackages(expectedPackages: ExpectedPackage.Any[] | undefined): void {
-	// Fill in ids of unnamed expectedPackage
+	// Compute and set contentVersionHash for each expectedPackage
 	if (expectedPackages) {
 		for (const expectedPackage of expectedPackages) {
 			expectedPackage.contentVersionHash = getContentVersionHash(expectedPackage)
 		}
 	}
 }
packages/corelib/src/dataModel/Old/ExpectedPackagesR52.ts (1)

16-28: Consider relocating to migration directory.

The warning comment clearly states this is "a snapshot...that should only be used in code performing fixup operations." As noted in a previous review, since these types are exclusively used during migration, relocating this file to meteor/server/migration/ would better reflect its purpose and keep migration-specific code co-located.

🧹 Nitpick comments (24)
packages/job-worker/src/ingest/__tests__/expectedPackages.test.ts (1)

114-137: Test name is now misleading after refactoring.

The test is named "Generates ExpectedPackages for a Part" but now only verifies that setExpectedPlayoutItems is called with an empty array. Since updateExpectedMediaAndPlayoutItemsForPartModel no longer handles expected packages (per the relevant snippet showing it only calls updateExpectedPlayoutItemsForPartModel), consider:

  1. Renaming the test to reflect its actual purpose, e.g., "Updates expected playout items for a Part"
  2. Optionally removing the unused expectedPackages property from the mock pieces if it's no longer relevant
Suggested test name update
-	test('Generates ExpectedPackages for a Part', async () => {
+	test('Updates expected playout items for a Part', async () => {
packages/job-worker/src/ingest/packageInfo.ts (2)

72-75: Rundown baseline regeneration is not implemented.

When regenerateRundownBaseline is set to true (for baseline piece types), no action is taken. This could lead to silent failures where package info updates for baseline objects don't trigger the expected regeneration.

Would you like me to open an issue to track this implementation, or is this intentionally deferred? If deferred, consider adding a log statement to make it visible when baseline regeneration is skipped.


92-92: Inconsistent log level.

This line uses logger.warn for an informational message about the number of changed segments. For consistency with line 66 (which uses logger.info), this should also be info level.

Suggested fix
-		logger.warn(`onUpdatedPackageInfoForRundown: Changed ${result?.changedSegmentIds.length ?? 0} segments`)
+		logger.info(`onUpdatedPackageInfoForRundown: Changed ${result?.changedSegmentIds.length ?? 0} segments`)
packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts (2)

18-21: Consider narrowing the type assertion.

The as any cast bypasses type safety entirely. If the issue is ReadonlyDeep not inferring correctly with the generic, a more targeted assertion like as ReadonlyDeep<IngestExpectedPackage<TPackageSource>[]> would preserve some type checking while satisfying the compiler.

Suggested refinement
 	get expectedPackages(): ReadonlyDeep<IngestExpectedPackage<TPackageSource>[]> {
-		// Typescript is not happy because of the generic
-		return this.#expectedPackages as any
+		// ReadonlyDeep inference struggles with generics
+		return this.#expectedPackages as ReadonlyDeep<IngestExpectedPackage<TPackageSource>[]>
 	}

104-107: Inconsistent change tracking compared to setExpectedPlayoutItems.

setExpectedPlayoutItems uses diffAndReturnLatestObjects to detect actual changes and only marks items as changed when they differ. In contrast, setExpectedPackages unconditionally sets #expectedPackagesHasChanges = true without comparing to previous values.

If this asymmetry is intentional (e.g., expected packages are always fully regenerated from ingest sources), the current approach is fine. Otherwise, consider aligning the behavior for consistency.

meteor/server/publications/pieceContentStatusUI/__tests__/checkPieceContentStatus.test.ts (1)

475-501: Consider adding test coverage for the expectedPackages path with ownership context.

The existing tests exercise the MediaObject fallback (no expectedPackages on pieces). Since the PR's core change is how packageOwnerId affects getExpectedPackageId lookup for pieces with expectedPackages, consider adding tests that verify:

  1. A piece with expectedPackages uses the correct candidatePackageId based on packageOwnerId
  2. Different packageOwnerId values (RundownId vs BucketId) resolve correctly

Would you like me to generate a test case that covers the expectedPackages path with the new ownership model?

meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts (1)

708-730: Minor: duplicate getSideEffect calls.

getSideEffect(expectedPackage, studio) is called twice - once for thumbnail (line 709) and once for preview (line 721). Consider extracting to a single call.

♻️ Optional refactor to avoid duplicate getSideEffect call
 				if (!thumbnailUrl) {
 					const sideEffect = getSideEffect(expectedPackage, studio)
 
 					thumbnailUrl = await getAssetUrlFromPackageContainerStatus(
 						studio.packageContainers,
 						getPackageContainerPackageStatus,
 						candidatePackageId,
 						sideEffect.thumbnailContainerId,
 						sideEffect.thumbnailPackageSettings?.path
 					)
-				}
 
-				if (!previewUrl) {
-					const sideEffect = getSideEffect(expectedPackage, studio)
-
-					previewUrl = await getAssetUrlFromPackageContainerStatus(
-						studio.packageContainers,
-						getPackageContainerPackageStatus,
-						candidatePackageId,
-						sideEffect.previewContainerId,
-						sideEffect.previewPackageSettings?.path
-					)
+					if (!previewUrl) {
+						previewUrl = await getAssetUrlFromPackageContainerStatus(
+							studio.packageContainers,
+							getPackageContainerPackageStatus,
+							candidatePackageId,
+							sideEffect.previewContainerId,
+							sideEffect.previewPackageSettings?.path
+						)
+					}
 				}
meteor/server/api/integration/expectedPackages.ts (1)

103-115: Consider improving the error message for multiple package IDs.

The projection optimization is a good improvement. However, the error message on line 115 will display the entire array (e.g., ExpectedPackages "id1,id2,id3" not found), which may be misleading since only one match is needed. Consider clarifying the message.

💡 Suggested improvement
 							if (!expPackage)
-									throw new Meteor.Error(404, `ExpectedPackages "${fromPackageIds}" not found`)
+									throw new Meteor.Error(404, `None of the ExpectedPackages "${fromPackageIds.join(', ')}" were found`)
packages/corelib/src/__tests__/hash.spec.ts (1)

193-196: Consider removing the uncertain comment.

The comment "This might fail - need to check if conversion is correct" is misleading since the test will pass: hashObj converts primitives via obj + '', producing "false" and "0" respectively, which are distinct strings. Consider removing the comment to avoid confusion for future maintainers.

Suggested fix
 		test('false should be different from 0', () => {
-			// This might fail - need to check if conversion is correct
 			expect(hashObj(false)).not.toBe(hashObj(0))
 		})
meteor/server/worker/jobQueue.ts (1)

374-374: Track the TODO for job timeouts.

The comment indicates timeout handling is not yet implemented. Long-running jobs could hang indefinitely. Consider creating an issue to track this enhancement.

Would you like me to open an issue to track implementing job timeout handling?

packages/job-worker/src/ingest/model/implementation/__tests__/SaveIngestModel.spec.ts (1)

232-246: Consider clarifying the bulkWrite operation count assertion.

Line 238's assertion expect(...operations[1].args[0]).toBe(1) depends on the mock's internal structure. A comment explaining what this verifies (e.g., "1 bulkWrite operation for the merged package") would improve test clarity.

Suggested improvement
 			// Verify only one insert (sources should be merged)
 			expect(context.mockCollections.ExpectedPackages.operations).toHaveLength(3)
 			expect(context.mockCollections.ExpectedPackages.operations[0].type).toBe('findFetch')
 			expect(context.mockCollections.ExpectedPackages.operations[1].type).toBe('bulkWrite')
-			expect(context.mockCollections.ExpectedPackages.operations[1].args[0]).toBe(1) // 1 operation
+			// Verify bulkWrite contains exactly 1 operation (both sources merged into single package)
+			expect(context.mockCollections.ExpectedPackages.operations[1].args[0]).toBe(1)
 			expect(context.mockCollections.ExpectedPackages.operations[2].type).toBe('insertOne')
packages/job-worker/src/playout/__tests__/expectedPackages.test.ts (1)

460-480: Consider optimizing empty array handling.

The test at lines 460-480 documents that the function updates even when pieceInstanceIds is already empty. This could be a minor performance optimization opportunity in the implementation to skip the update when there are no changes needed, but it's not critical.

packages/job-worker/src/ingest/expectedPackages.ts (1)

256-263: Cleanup logic is correct but consider adding a comment about future playout references.

The cleanUpUnusedPackagesInBucket function removes packages with empty ingestSources. The comment on line 261 mentions that bucket packages can't currently be referenced by playoutSources, but this might change. Consider adding a TODO or tracking issue if this is planned.

packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts (1)

457-470: Hardcoded partInstanceId may cause test inaccuracies.

The helper always sets partInstanceId to 'partInstance0' regardless of the actual partInstanceId passed to createPartInstanceWithPieceInstances. This could cause issues if tests rely on the PieceInstance.partInstanceId matching the actual PartInstance._id.

Consider parameterizing this:

Proposed fix
 		function createPieceInstanceWithExpectedPackages(
 			pieceInstanceId: string,
-			expectedPackages: ExpectedPackage.Base[]
+			expectedPackages: ExpectedPackage.Base[],
+			partInstanceId: string = 'partInstance0'
 		): PieceInstance {
 			return {
 				_id: protectString<PieceInstanceId>(pieceInstanceId),
 				rundownId: rundownId,
-				partInstanceId: protectString<PartInstanceId>('partInstance0'),
+				partInstanceId: protectString<PartInstanceId>(partInstanceId),
 				piece: {
 					_id: protectString(`piece_${pieceInstanceId}`),
 					expectedPackages,
 				},
 			} as unknown as PieceInstance
 		}
meteor/server/worker/worker.ts (1)

229-240: Consider adding options parameter to QueueIngestJob for API consistency.

QueueStudioJob now accepts an optional options?: QueueJobOptions parameter, but QueueIngestJob (lines 249-257) doesn't have this parameter. If there's no specific reason to exclude it, consider adding it for API consistency.

♻️ Suggested change
 export async function QueueIngestJob<T extends keyof IngestJobFunc>(
 	jobName: T,
 	studioId: StudioId,
-	jobParameters: Parameters<IngestJobFunc[T]>[0]
+	jobParameters: Parameters<IngestJobFunc[T]>[0],
+	options?: QueueJobOptions
 ): Promise<WorkerJob<ReturnType<IngestJobFunc[T]>>> {
 	if (!studioId) throw new Meteor.Error(500, 'Missing studioId')

 	const now = getCurrentTime()
-	return queueManager.queueJobAndWrapResult(getIngestQueueName(studioId), jobName, jobParameters, now)
+	return queueManager.queueJobAndWrapResult(getIngestQueueName(studioId), jobName, jobParameters, now, options)
 }
meteor/server/migration/X_X_X.ts (2)

176-194: Non-atomic remove/insert could leave orphaned state on failure.

If the process crashes between removeAsync (line 176) and insertAsync (line 179), the package will be lost. Consider using a transaction or replaceOne operation instead.

Suggested approach using replaceOne
-				await ExpectedPackages.mutableCollection.removeAsync(pkg._id)
-
 				if (ingestSource) {
-					await ExpectedPackages.mutableCollection.insertAsync({
+					await ExpectedPackages.mutableCollection.updateAsync(
+						{ _id: pkg._id },
+						{
 						_id: pkg._id, // Preserve the old id to ensure references aren't broken. This will be 'corrected' upon first ingest operation
 						studioId: pkg.studioId,
 						rundownId: rundownId,
 						bucketId: bucketId,
 						package: {
 							...(pkg as any), // Some fields should be pruned off this, but this is fine
 							_id: pkg.blueprintPackageId,
 						},
 						created: pkg.created,
 						ingestSources: [ingestSource],
 						playoutSources: {
 							pieceInstanceIds: [],
 						},
-					} satisfies Complete<ExpectedPackageDB>)
+						} satisfies Complete<ExpectedPackageDB>,
+						{ upsert: true }
+					)
+				} else {
+					await ExpectedPackages.mutableCollection.removeAsync(pkg._id)
 				}

184-187: Unpruned legacy fields will persist in migrated documents.

The (pkg as any) spread includes old top-level fields like fromPieceType, pieceId, segmentId, etc. in the package object. While the comment acknowledges this, these stale fields will remain until re-ingest. Consider explicitly picking only the needed fields to keep the data clean.

packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts (1)

237-249: Address the TODO comment for test coverage.

The $addToSet with $each operator usage is correct, but the TODO comment on line 242 indicates this specific MongoDB operation may need additional test coverage. Ensure the test suite (referenced in SavePlayoutModel.spec.ts) validates this update behavior.

Would you like me to help generate test cases for this $addToSet operation, or should I open an issue to track this?

packages/job-worker/src/playout/expectedPackages.ts (1)

78-87: Clarify the purpose of isPackageReferencedByPlayout check with empty pieceInstanceIds.

Looking at the isPackageReferencedByPlayout function from the snippets:

export function isPackageReferencedByPlayout(expectedPackage: Pick<ExpectedPackageDB, 'playoutSources'>): boolean {
	return expectedPackage.playoutSources.pieceInstanceIds.length > 0
}

The call on lines 80-87 passes a fake package with pieceInstanceIds: [], which will always return false from isPackageReferencedByPlayout. Combined with the ingestSources.length === 0 check on line 79, this effectively means: "delete if no ingest sources exist."

If this is the intended behavior, consider simplifying to just expectedPackage.ingestSources.length === 0 for clarity. If playoutSources may have additional fields in the future that would affect this check, consider adding a comment explaining the forward-compatibility intent.

meteor/server/api/ingest/packageInfo.ts (2)

56-60: Confusing error message - the guard validates correctly but the log is misleading.

The error message logs pkg.rundownId which will be undefined (the very condition being checked). Consider making the message clearer:

Suggested improvement
 function onUpdatedPackageInfoForRundownDebounce(pkg: ExpectedPackageDB) {
 	if (!pkg.rundownId) {
-		logger.error(`Updating ExpectedPackage "${pkg._id}" for Rundown "${pkg.rundownId}" not possible`)
+		logger.error(`Updating ExpectedPackage "${pkg._id}" not possible: missing rundownId`)
 		return
 	}

115-119: Same misleading error message pattern.

Suggested improvement
 function onUpdatedPackageInfoForBucketItemDebounce(pkg: ExpectedPackageDB, source: ExpectedPackageIngestSourceBucket) {
 	if (!pkg.bucketId) {
-		logger.error(`Updating ExpectedPackage "${pkg._id}" for Bucket "${pkg.bucketId}" not possible`)
+		logger.error(`Updating ExpectedPackage "${pkg._id}" not possible: missing bucketId`)
 		return
 	}
packages/job-worker/src/ingest/model/IngestExpectedPackage.ts (1)

27-65: Consider clarifying or verifying deduplication scope.

The insertedPackagesForSource Set (line 39) is scoped to a single addPackagesWithSource call, meaning deduplication only occurs within that call. If identical packages are added via separate calls, duplicates will accumulate in #packages.

This may be intentional since each call represents a different source, but if cross-call deduplication is needed, consider lifting the Set to a class field or documenting this behavior.

packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts (1)

116-129: Type cast includes unprojected field.

Line 128 casts to Pick<ExpectedPackageDB, '_id' | 'created' | 'playoutSources'>[] but the projection (lines 123-126) only fetches _id and playoutSources, not created. While this doesn't cause runtime issues since created isn't accessed, the type is misleading.

🛠️ Suggested fix
-	)) as Pick<ExpectedPackageDB, '_id' | 'created' | 'playoutSources'>[]
+	)) as Pick<ExpectedPackageDB, '_id' | 'playoutSources'>[]
packages/corelib/src/dataModel/ExpectedPackages.ts (1)

48-51: Mutually exclusive ownership fields need runtime validation.

The comment states rundownId and bucketId must not both be set, but there's no compile-time enforcement. Consider adding a runtime assertion or using a discriminated union pattern if this invariant is critical.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2500780 and 03346be.

📒 Files selected for processing (80)
  • meteor/server/api/__tests__/cleanup.test.ts
  • meteor/server/api/ingest/debug.ts
  • meteor/server/api/ingest/packageInfo.ts
  • meteor/server/api/integration/expectedPackages.ts
  • meteor/server/collections/packages-media.ts
  • meteor/server/migration/1_50_0.ts
  • meteor/server/migration/X_X_X.ts
  • meteor/server/publications/packageManager/expectedPackages/contentCache.ts
  • meteor/server/publications/packageManager/expectedPackages/generate.ts
  • meteor/server/publications/packageManager/expectedPackages/publication.ts
  • meteor/server/publications/pieceContentStatusUI/__tests__/checkPieceContentStatus.test.ts
  • meteor/server/publications/pieceContentStatusUI/bucket/regenerateForItem.ts
  • meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts
  • meteor/server/publications/pieceContentStatusUI/rundown/regenerateItems.ts
  • meteor/server/worker/__tests__/jobQueue.test.ts
  • meteor/server/worker/jobQueue.ts
  • meteor/server/worker/worker.ts
  • packages/corelib/src/__tests__/hash.spec.ts
  • packages/corelib/src/dataModel/ExpectedPackageWorkStatuses.ts
  • packages/corelib/src/dataModel/ExpectedPackages.ts
  • packages/corelib/src/dataModel/Old/ExpectedPackagesR52.ts
  • packages/corelib/src/dataModel/PackageInfos.ts
  • packages/corelib/src/dataModel/PieceInstance.ts
  • packages/corelib/src/hash.ts
  • packages/corelib/src/mongo.ts
  • packages/corelib/src/pubsub.ts
  • packages/corelib/src/snapshots.ts
  • packages/corelib/src/worker/ingest.ts
  • packages/corelib/src/worker/studio.ts
  • packages/job-worker/src/__mocks__/collection.ts
  • packages/job-worker/src/__mocks__/context.ts
  • packages/job-worker/src/blueprints/context/__tests__/watchedPackages.test.ts
  • packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts
  • packages/job-worker/src/blueprints/context/watchedPackages.ts
  • packages/job-worker/src/blueprints/postProcess.ts
  • packages/job-worker/src/ingest/__tests__/expectedPackages.test.ts
  • packages/job-worker/src/ingest/bucket/bucketAdlibs.ts
  • packages/job-worker/src/ingest/bucket/import.ts
  • packages/job-worker/src/ingest/commit.ts
  • packages/job-worker/src/ingest/expectedPackages.ts
  • packages/job-worker/src/ingest/generationRundown.ts
  • packages/job-worker/src/ingest/generationSegment.ts
  • packages/job-worker/src/ingest/model/IngestExpectedPackage.ts
  • packages/job-worker/src/ingest/model/IngestModel.ts
  • packages/job-worker/src/ingest/model/IngestPartModel.ts
  • packages/job-worker/src/ingest/model/implementation/DocumentChangeTracker.ts
  • packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts
  • packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts
  • packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts
  • packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts
  • packages/job-worker/src/ingest/model/implementation/LoadIngestModel.ts
  • packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts
  • packages/job-worker/src/ingest/model/implementation/__tests__/SaveIngestModel.spec.ts
  • packages/job-worker/src/ingest/packageInfo.ts
  • packages/job-worker/src/ingest/runOperation.ts
  • packages/job-worker/src/ipc.ts
  • packages/job-worker/src/jobs/index.ts
  • packages/job-worker/src/manager.ts
  • packages/job-worker/src/playout/__tests__/expectedPackages.test.ts
  • packages/job-worker/src/playout/adlibAction.ts
  • packages/job-worker/src/playout/expectedPackages.ts
  • packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts
  • packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts
  • packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts
  • packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts
  • packages/job-worker/src/playout/snapshot.ts
  • packages/job-worker/src/playout/timeline/generate.ts
  • packages/job-worker/src/studio/model/StudioBaselineHelper.ts
  • packages/job-worker/src/studio/model/StudioPlayoutModel.ts
  • packages/job-worker/src/studio/model/StudioPlayoutModelImpl.ts
  • packages/job-worker/src/workers/context/JobContextImpl.ts
  • packages/job-worker/src/workers/context/util.ts
  • packages/job-worker/src/workers/ingest/jobs.ts
  • packages/job-worker/src/workers/studio/jobs.ts
  • packages/meteor-lib/src/collections/ExpectedPackages.ts
  • packages/shared-lib/src/package-manager/package.ts
  • packages/shared-lib/src/package-manager/publications.ts
  • packages/webui/src/client/ui/Settings/DevicePackageManagerSettings.tsx
  • packages/webui/src/client/ui/Status/package-status/PackageStatus.tsx
  • packages/webui/src/client/ui/Status/package-status/index.tsx
💤 Files with no reviewable changes (2)
  • meteor/server/api/ingest/debug.ts
  • packages/corelib/src/worker/ingest.ts
🧰 Additional context used
🧬 Code graph analysis (49)
packages/webui/src/client/ui/Status/package-status/PackageStatus.tsx (1)
packages/shared-lib/src/package-manager/package.ts (3)
  • Any (24-28)
  • Any (395-404)
  • Any (576-585)
packages/corelib/src/snapshots.ts (2)
packages/corelib/src/dataModel/ExpectedPackages.ts (1)
  • ExpectedPackageDB (42-69)
packages/corelib/src/dataModel/Old/ExpectedPackagesR52.ts (1)
  • ExpectedPackageDB (39-43)
packages/corelib/src/pubsub.ts (2)
packages/corelib/src/dataModel/ExpectedPackages.ts (1)
  • ExpectedPackageDB (42-69)
packages/corelib/src/dataModel/Old/ExpectedPackagesR52.ts (1)
  • ExpectedPackageDB (39-43)
packages/job-worker/src/workers/studio/jobs.ts (1)
packages/job-worker/src/playout/expectedPackages.ts (1)
  • handleCleanupOrphanedExpectedPackageReferences (12-133)
packages/job-worker/src/__mocks__/collection.ts (2)
packages/corelib/src/mongo.ts (1)
  • MongoQuery (60-60)
packages/job-worker/src/db/collections.ts (1)
  • MongoQuery (48-48)
packages/webui/src/client/ui/Status/package-status/index.tsx (3)
packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts (1)
  • expectedPackages (18-21)
packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts (1)
  • expectedPackages (90-92)
packages/shared-lib/src/lib/protectedString.ts (1)
  • unprotectString (35-37)
packages/job-worker/src/ingest/model/implementation/__tests__/SaveIngestModel.spec.ts (5)
packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts (1)
  • rundownId (106-108)
packages/shared-lib/src/lib/protectedString.ts (1)
  • protectString (21-23)
packages/shared-lib/src/core/model/Ids.ts (2)
  • RundownId (21-21)
  • PartId (37-37)
packages/corelib/src/dataModel/ExpectedPackages.ts (2)
  • getExpectedPackageId (158-172)
  • ExpectedPackageDB (42-69)
packages/corelib/src/dataModel/Ids.ts (2)
  • PieceId (55-55)
  • SegmentId (67-67)
meteor/server/worker/__tests__/jobQueue.test.ts (1)
meteor/server/worker/jobQueue.ts (2)
  • queueName (65-79)
  • queueName (247-314)
packages/job-worker/src/ingest/model/IngestPartModel.ts (1)
packages/job-worker/src/ingest/model/IngestExpectedPackage.ts (1)
  • IngestExpectedPackage (15-25)
packages/corelib/src/dataModel/PieceInstance.ts (1)
packages/shared-lib/src/core/model/Ids.ts (1)
  • ExpectedPackageId (29-29)
packages/job-worker/src/ingest/commit.ts (1)
packages/job-worker/src/ingest/model/IngestModel.ts (2)
  • IngestModel (141-254)
  • IngestDatabasePersistedModel (258-263)
packages/job-worker/src/playout/adlibAction.ts (1)
packages/job-worker/src/blueprints/context/watchedPackages.ts (2)
  • WatchedPackagesHelper (15-189)
  • context (134-145)
packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts (1)
packages/corelib/src/dataModel/ExpectedPackages.ts (2)
  • ExpectedPackageDB (42-69)
  • getExpectedPackageId (158-172)
packages/job-worker/src/ingest/model/implementation/LoadIngestModel.ts (1)
packages/job-worker/src/ingest/model/IngestModel.ts (2)
  • IngestModel (141-254)
  • IngestDatabasePersistedModel (258-263)
packages/job-worker/src/playout/expectedPackages.ts (4)
packages/corelib/src/worker/studio.ts (1)
  • CleanupOrphanedExpectedPackageReferencesProps (378-381)
packages/corelib/src/dataModel/ExpectedPackages.ts (2)
  • ExpectedPackageDB (42-69)
  • isPackageReferencedByPlayout (174-176)
packages/corelib/src/dataModel/PieceInstance.ts (1)
  • PieceInstance (34-80)
packages/shared-lib/src/core/model/Ids.ts (2)
  • PieceInstanceId (15-15)
  • ExpectedPackageId (29-29)
packages/job-worker/src/jobs/index.ts (1)
packages/corelib/src/worker/studio.ts (1)
  • StudioJobFunc (387-441)
meteor/server/publications/pieceContentStatusUI/__tests__/checkPieceContentStatus.test.ts (4)
packages/shared-lib/src/lib/protectedString.ts (1)
  • protectString (21-23)
packages/shared-lib/src/core/model/Ids.ts (1)
  • RundownId (21-21)
meteor/server/publications/pieceContentStatusUI/messageFactory.ts (1)
  • PieceContentStatusMessageFactory (88-124)
meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts (1)
  • checkPieceContentStatusAndDependencies (228-349)
packages/job-worker/src/ingest/__tests__/expectedPackages.test.ts (1)
packages/job-worker/src/ingest/expectedPackages.ts (1)
  • updateExpectedMediaAndPlayoutItemsForPartModel (27-29)
packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts (3)
packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts (6)
  • adLibPieces (79-81)
  • expectedPackages (90-92)
  • part (71-73)
  • pieces (75-77)
  • adLibActions (83-85)
  • IngestPartModelImpl (21-235)
packages/job-worker/src/ingest/model/IngestExpectedPackage.ts (2)
  • IngestExpectedPackage (15-25)
  • ExpectedPackageCollector (27-66)
packages/corelib/src/dataModel/ExpectedPackages.ts (3)
  • ExpectedPackageIngestSourcePart (136-136)
  • ExpectedPackageIngestSourcePiece (94-102)
  • ExpectedPackageIngestSourceAdlibAction (103-111)
packages/corelib/src/worker/studio.ts (1)
packages/shared-lib/src/core/model/Ids.ts (2)
  • RundownPlaylistId (24-24)
  • RundownId (21-21)
packages/job-worker/src/ingest/runOperation.ts (1)
packages/job-worker/src/ingest/model/IngestModel.ts (2)
  • IngestModel (141-254)
  • IngestDatabasePersistedModel (258-263)
packages/job-worker/src/ipc.ts (2)
meteor/server/worker/jobQueue.ts (5)
  • jobFinished (82-102)
  • interruptJobStream (179-199)
  • queueName (65-79)
  • queueName (247-314)
  • getNextJob (139-177)
packages/job-worker/src/manager.ts (1)
  • JobManager (5-21)
packages/job-worker/src/blueprints/postProcess.ts (1)
packages/job-worker/src/ingest/expectedPackages.ts (1)
  • sanitiseExpectedPackages (275-282)
meteor/server/collections/packages-media.ts (2)
meteor/server/collections/indices.ts (1)
  • registerIndex (20-32)
packages/webui/src/client/collections/index.ts (1)
  • ExpectedPackages (62-62)
packages/job-worker/src/workers/context/JobContextImpl.ts (4)
packages/corelib/src/worker/ingest.ts (1)
  • getIngestQueueName (329-331)
packages/corelib/src/worker/studio.ts (2)
  • StudioJobFunc (387-441)
  • getStudioQueueName (443-445)
packages/job-worker/src/jobs/index.ts (1)
  • QueueJobOptions (27-38)
packages/corelib/src/worker/events.ts (2)
  • EventsJobFunc (22-25)
  • getEventsQueueName (27-29)
meteor/server/api/__tests__/cleanup.test.ts (1)
packages/webui/src/client/collections/index.ts (1)
  • Pieces (86-86)
packages/corelib/src/dataModel/ExpectedPackageWorkStatuses.ts (1)
packages/shared-lib/src/core/model/Ids.ts (1)
  • ExpectedPackageId (29-29)
meteor/server/migration/1_50_0.ts (2)
packages/corelib/src/dataModel/ExpectedPackages.ts (1)
  • ExpectedPackageDB (42-69)
packages/corelib/src/dataModel/Old/ExpectedPackagesR52.ts (1)
  • ExpectedPackageDB (39-43)
meteor/server/publications/packageManager/expectedPackages/contentCache.ts (5)
packages/corelib/src/dataModel/PieceInstance.ts (1)
  • PieceInstance (34-80)
packages/corelib/src/mongo.ts (1)
  • MongoFieldSpecifierOnesStrict (29-35)
packages/corelib/src/dataModel/ExpectedPackages.ts (1)
  • ExpectedPackageDB (42-69)
meteor/server/publications/lib/ReactiveCacheCollection.ts (1)
  • ReactiveCacheCollection (9-153)
meteor/server/publications/packageManager/expectedPackages/contentObserver.ts (1)
  • cache (113-115)
meteor/server/api/integration/expectedPackages.ts (2)
meteor/server/collections/packages-media.ts (1)
  • ExpectedPackages (12-14)
packages/corelib/src/dataModel/ExpectedPackages.ts (1)
  • ExpectedPackageDB (42-69)
meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts (4)
packages/shared-lib/src/core/model/Ids.ts (1)
  • RundownId (21-21)
packages/corelib/src/dataModel/Ids.ts (1)
  • BucketId (17-17)
packages/corelib/src/dataModel/ExpectedPackages.ts (1)
  • getExpectedPackageId (158-172)
packages/meteor-lib/src/collections/ExpectedPackages.ts (1)
  • getSideEffect (58-72)
packages/corelib/src/dataModel/Old/ExpectedPackagesR52.ts (3)
packages/corelib/src/dataModel/ExpectedPackages.ts (1)
  • ExpectedPackageDB (42-69)
packages/shared-lib/src/core/model/Ids.ts (3)
  • ExpectedPackageId (29-29)
  • PartId (37-37)
  • RundownId (21-21)
packages/corelib/src/dataModel/Ids.ts (7)
  • PieceId (55-55)
  • SegmentId (67-67)
  • AdLibActionId (12-12)
  • RundownBaselineAdLibActionId (58-58)
  • BucketId (17-17)
  • BucketAdLibId (18-18)
  • BucketAdLibActionId (19-19)
packages/job-worker/src/ingest/generationSegment.ts (2)
packages/job-worker/src/ingest/expectedPackages.ts (1)
  • updateExpectedMediaAndPlayoutItemsForPartModel (27-29)
packages/job-worker/src/blueprints/context/watchedPackages.ts (1)
  • context (134-145)
packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts (3)
packages/shared-lib/src/core/model/Ids.ts (2)
  • ExpectedPackageId (29-29)
  • RundownId (21-21)
packages/corelib/src/dataModel/PieceInstance.ts (1)
  • PieceInstance (34-80)
packages/corelib/src/dataModel/ExpectedPackages.ts (1)
  • getExpectedPackageId (158-172)
packages/corelib/src/__tests__/hash.spec.ts (1)
packages/corelib/src/hash.ts (1)
  • hashObj (10-37)
packages/shared-lib/src/package-manager/publications.ts (1)
packages/shared-lib/src/core/model/Ids.ts (1)
  • ExpectedPackageId (29-29)
packages/job-worker/src/ingest/packageInfo.ts (2)
packages/corelib/src/lib.ts (1)
  • assertNever (27-27)
meteor/server/logging.ts (1)
  • logger (146-146)
packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts (3)
packages/job-worker/src/ingest/model/IngestExpectedPackage.ts (1)
  • IngestExpectedPackage (15-25)
packages/corelib/src/dataModel/ExpectedPackages.ts (2)
  • ExpectedPackageDB (42-69)
  • isPackageReferencedByPlayout (174-176)
packages/corelib/src/lib.ts (1)
  • normalizeArrayToMap (168-174)
packages/job-worker/src/ingest/generationRundown.ts (6)
packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts (1)
  • expectedPackages (18-21)
packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts (3)
  • expectedPackages (90-92)
  • adLibPieces (79-81)
  • adLibActions (83-85)
packages/job-worker/src/ingest/expectedPackages.ts (1)
  • updateExpectedMediaAndPlayoutItemsForRundownBaseline (31-37)
packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts (1)
  • rundownId (106-108)
packages/job-worker/src/ingest/model/IngestExpectedPackage.ts (2)
  • IngestExpectedPackage (15-25)
  • ExpectedPackageCollector (27-66)
packages/corelib/src/dataModel/ExpectedPackages.ts (5)
  • ExpectedPackageIngestSourceRundownBaseline (142-146)
  • ExpectedPackageIngestSourceBaselineObjects (127-129)
  • ExpectedPackageIngestSourceBaselineAdlibPiece (117-121)
  • ExpectedPackageIngestSourceBaselineAdlibAction (122-126)
  • ExpectedPackageIngestSourceBaselinePiece (112-116)
packages/job-worker/src/manager.ts (1)
packages/job-worker/src/jobs/index.ts (1)
  • QueueJobOptions (27-38)
packages/job-worker/src/ingest/model/IngestExpectedPackage.ts (5)
packages/corelib/src/dataModel/ExpectedPackages.ts (3)
  • ExpectedPackageIngestSourcePart (136-136)
  • ExpectedPackageIngestSourceRundownBaseline (142-146)
  • getExpectedPackageId (158-172)
packages/shared-lib/src/core/model/Ids.ts (2)
  • ExpectedPackageId (29-29)
  • RundownId (21-21)
packages/corelib/src/dataModel/Ids.ts (1)
  • BucketId (17-17)
packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts (1)
  • expectedPackages (18-21)
packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts (1)
  • expectedPackages (90-92)
packages/job-worker/src/__mocks__/context.ts (2)
packages/corelib/src/worker/studio.ts (1)
  • StudioJobFunc (387-441)
packages/job-worker/src/jobs/index.ts (1)
  • QueueJobOptions (27-38)
packages/webui/src/client/ui/Settings/DevicePackageManagerSettings.tsx (1)
packages/webui/src/client/lib/ModalDialog.tsx (1)
  • doModalDialog (393-399)
packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts (5)
packages/corelib/src/dataModel/Ids.ts (1)
  • ExpectedPlayoutItemId (28-28)
packages/job-worker/src/ingest/model/implementation/utils.ts (4)
  • DocumentChanges (76-80)
  • getDocumentChanges (82-109)
  • setValuesAndTrackChanges (46-68)
  • diffAndReturnLatestObjects (13-44)
packages/shared-lib/src/core/model/Ids.ts (2)
  • RundownId (21-21)
  • PartId (37-37)
packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts (1)
  • rundownId (106-108)
packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts (2)
  • expectedPlayoutItems (87-89)
  • expectedPackages (90-92)
packages/job-worker/src/blueprints/context/watchedPackages.ts (4)
packages/shared-lib/src/core/model/Ids.ts (2)
  • ExpectedPackageId (29-29)
  • RundownId (21-21)
packages/job-worker/src/ingest/model/IngestExpectedPackage.ts (1)
  • IngestExpectedPackage (15-25)
packages/corelib/src/dataModel/Ids.ts (1)
  • BucketId (17-17)
packages/shared-lib/src/lib/protectedString.ts (1)
  • unprotectObjectArray (68-70)
packages/job-worker/src/ingest/bucket/bucketAdlibs.ts (1)
packages/job-worker/src/ingest/expectedPackages.ts (1)
  • cleanUpExpectedPackagesForBucketAdLibs (226-254)
packages/job-worker/src/studio/model/StudioBaselineHelper.ts (5)
packages/shared-lib/src/core/model/Ids.ts (1)
  • ExpectedPackageId (29-29)
packages/job-worker/src/ingest/model/IngestExpectedPackage.ts (1)
  • IngestExpectedPackage (15-25)
packages/corelib/src/dataModel/ExpectedPackages.ts (3)
  • ExpectedPackageIngestSourceStudioBaseline (131-134)
  • getExpectedPackageId (158-172)
  • ExpectedPackageDB (42-69)
packages/job-worker/src/ingest/expectedPackages.ts (1)
  • sanitiseExpectedPackages (275-282)
packages/corelib/src/dataModel/Old/ExpectedPackagesR52.ts (1)
  • ExpectedPackageDB (39-43)
packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts (2)
packages/corelib/src/lib.ts (1)
  • groupByToMapFunc (227-244)
packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts (1)
  • writeExpectedPackagesForPlayoutSources (158-290)
packages/job-worker/src/ingest/bucket/import.ts (5)
packages/job-worker/src/blueprints/context/watchedPackages.ts (2)
  • context (134-145)
  • WatchedPackagesHelper (15-189)
packages/job-worker/src/ingest/expectedPackages.ts (1)
  • cleanUpExpectedPackagesForBucketAdLibs (226-254)
packages/corelib/src/dataModel/Ids.ts (1)
  • BucketId (17-17)
packages/blueprints-integration/src/documents/adlibPiece.ts (1)
  • IBlueprintAdLibPiece (3-27)
packages/blueprints-integration/src/action.ts (1)
  • IBlueprintActionManifest (75-128)

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

})
} else {
// There should be a worker waiting, its `getNextJob` might not have reached us yet
// So we psuh a `null` job at the start so that it interrupts immediately
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo in comment: "psuh" should be "push".

Suggested fix
-			// So we psuh a `null` job at the start so that it interrupts immediately
+			// So we push a `null` job at the start so that it interrupts immediately
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// So we psuh a `null` job at the start so that it interrupts immediately
// So we push a `null` job at the start so that it interrupts immediately
🤖 Prompt for AI Agents
In `@meteor/server/worker/jobQueue.ts` at line 196, Update the inline comment in
jobQueue.ts that currently reads "So we psuh a `null` job at the start so that
it interrupts immediately" to correct the typo to "push" (e.g., "So we push a
`null` job at the start so that it interrupts immediately") near the code that
enqueues the null job in the job queue processing logic.

Comment on lines +14 to +18
if (Array.isArray(obj)) {
// For arrays, we care about the order, and should preserve undefined
const strs = obj.map((val, i) => hashObj(`${i}:${hashObj(val)}`))

return getHash(strs.join('|'))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Redundant nested hashObj call for array elements.

Line 16 applies hashObj twice: once to compute the element's hash, and again to hash the resulting "index:hash" string. This double-hashing is likely unintentional and differs from the object branch (line 31) which directly pushes ${key}:${hashObj(val)} without an outer hashObj call.

🔧 Suggested fix
 	if (Array.isArray(obj)) {
 		// For arrays, we care about the order, and should preserve undefined
-		const strs = obj.map((val, i) => hashObj(`${i}:${hashObj(val)}`))
+		const strs = obj.map((val, i) => `${i}:${hashObj(val)}`)

 		return getHash(strs.join('|'))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Array.isArray(obj)) {
// For arrays, we care about the order, and should preserve undefined
const strs = obj.map((val, i) => hashObj(`${i}:${hashObj(val)}`))
return getHash(strs.join('|'))
if (Array.isArray(obj)) {
// For arrays, we care about the order, and should preserve undefined
const strs = obj.map((val, i) => `${i}:${hashObj(val)}`)
return getHash(strs.join('|'))
🤖 Prompt for AI Agents
In `@packages/corelib/src/hash.ts` around lines 14 - 18, The Array branch in the
Array.isArray(obj) block double-hashes elements by calling hashObj on the
`"index:hash"` string; change the mapping in that block so it produces the
string `${i}:${hashObj(val)}` (like the object branch) and then call getHash on
strs.join('|') once, ensuring order and undefined preservation; update the
mapping that defines strs and leave getHash(strs.join('|')) as the final hash
call (referencing hashObj, strs, getHash).

Comment on lines +207 to +256
it('does not return package info for packages with listenToPackageInfoUpdates: false', async () => {
const context = setupDefaultJobEnvironment()
const rundownId = protectString<RundownId>('rundown1')
const packageId = protectString<ExpectedPackageId>('pkg1')

await context.mockCollections.ExpectedPackages.insertOne(
literal<ExpectedPackageDB>({
_id: packageId,
studioId: context.studioId,
rundownId: rundownId,
bucketId: null,
package: { _id: 'package1' } as any,
ingestSources: [
{
fromPieceType: ExpectedPackageDBType.PIECE,
pieceId: protectString('piece1'),
blueprintPackageId: 'package1',
listenToPackageInfoUpdates: false,
} as any,
],
playoutSources: {
pieceInstanceIds: [],
},
created: 1000,
})
)

await context.mockCollections.PackageInfos.insertOne(
literal<PackageInfoDB>({
_id: protectString('info1'),
studioId: context.studioId,
packageId: packageId,
deviceId: mockDeviceId,
type: PackageInfo.Type.SCAN,
expectedContentVersionHash: 'abc123',
actualContentVersionHash: 'abc123',
payload: {} as any,
})
)

const helper = await WatchedPackagesHelper.create(context, rundownId, null, {
fromPieceType: ExpectedPackageDBType.PIECE,
pieceId: protectString('piece1'),
})

// Package should still be found (create doesn't filter by listenToPackageInfoUpdates)
expect(helper.hasPackage(packageId)).toBe(true)
// And package info should be available
expect(helper.getPackageInfo('package1')).toHaveLength(1)
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test name/assertion mismatch - test proves opposite of description.

The test is named "does not return package info for packages with listenToPackageInfoUpdates: false" but the assertions at lines 253-255 show that:

  1. The package IS found (hasPackage returns true)
  2. Package info IS returned (getPackageInfo has length 1)

The comment at line 254 even states "And package info should be available". Either the test name should be updated to reflect actual behavior, or the assertions need to match the expected behavior described in the name.

Suggested fix for test name
-		it('does not return package info for packages with listenToPackageInfoUpdates: false', async () => {
+		it('returns package info even when listenToPackageInfoUpdates is false in create', async () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('does not return package info for packages with listenToPackageInfoUpdates: false', async () => {
const context = setupDefaultJobEnvironment()
const rundownId = protectString<RundownId>('rundown1')
const packageId = protectString<ExpectedPackageId>('pkg1')
await context.mockCollections.ExpectedPackages.insertOne(
literal<ExpectedPackageDB>({
_id: packageId,
studioId: context.studioId,
rundownId: rundownId,
bucketId: null,
package: { _id: 'package1' } as any,
ingestSources: [
{
fromPieceType: ExpectedPackageDBType.PIECE,
pieceId: protectString('piece1'),
blueprintPackageId: 'package1',
listenToPackageInfoUpdates: false,
} as any,
],
playoutSources: {
pieceInstanceIds: [],
},
created: 1000,
})
)
await context.mockCollections.PackageInfos.insertOne(
literal<PackageInfoDB>({
_id: protectString('info1'),
studioId: context.studioId,
packageId: packageId,
deviceId: mockDeviceId,
type: PackageInfo.Type.SCAN,
expectedContentVersionHash: 'abc123',
actualContentVersionHash: 'abc123',
payload: {} as any,
})
)
const helper = await WatchedPackagesHelper.create(context, rundownId, null, {
fromPieceType: ExpectedPackageDBType.PIECE,
pieceId: protectString('piece1'),
})
// Package should still be found (create doesn't filter by listenToPackageInfoUpdates)
expect(helper.hasPackage(packageId)).toBe(true)
// And package info should be available
expect(helper.getPackageInfo('package1')).toHaveLength(1)
})
it('returns package info even when listenToPackageInfoUpdates is false in create', async () => {
const context = setupDefaultJobEnvironment()
const rundownId = protectString<RundownId>('rundown1')
const packageId = protectString<ExpectedPackageId>('pkg1')
await context.mockCollections.ExpectedPackages.insertOne(
literal<ExpectedPackageDB>({
_id: packageId,
studioId: context.studioId,
rundownId: rundownId,
bucketId: null,
package: { _id: 'package1' } as any,
ingestSources: [
{
fromPieceType: ExpectedPackageDBType.PIECE,
pieceId: protectString('piece1'),
blueprintPackageId: 'package1',
listenToPackageInfoUpdates: false,
} as any,
],
playoutSources: {
pieceInstanceIds: [],
},
created: 1000,
})
)
await context.mockCollections.PackageInfos.insertOne(
literal<PackageInfoDB>({
_id: protectString('info1'),
studioId: context.studioId,
packageId: packageId,
deviceId: mockDeviceId,
type: PackageInfo.Type.SCAN,
expectedContentVersionHash: 'abc123',
actualContentVersionHash: 'abc123',
payload: {} as any,
})
)
const helper = await WatchedPackagesHelper.create(context, rundownId, null, {
fromPieceType: ExpectedPackageDBType.PIECE,
pieceId: protectString('piece1'),
})
// Package should still be found (create doesn't filter by listenToPackageInfoUpdates)
expect(helper.hasPackage(packageId)).toBe(true)
// And package info should be available
expect(helper.getPackageInfo('package1')).toHaveLength(1)
})
🤖 Prompt for AI Agents
In `@packages/job-worker/src/blueprints/context/__tests__/watchedPackages.test.ts`
around lines 207 - 256, The test name/assertion mismatch: update the test
description to reflect actual behavior (the test currently asserts that the
package is found and package info is returned), e.g. change the it(...) string
to "returns package info for packages with listenToPackageInfoUpdates: false"
(or similar), keeping the existing assertions that call
WatchedPackagesHelper.create, hasPackage(packageId) and
getPackageInfo('package1'); alternatively, if you intended to assert that no
package info is returned, change the expectations to
expect(helper.hasPackage(packageId)).toBe(true) and
expect(helper.getPackageInfo('package1')).toHaveLength(0) and adjust setup
accordingly, referencing the test case around WatchedPackagesHelper.create,
hasPackage, and getPackageInfo.

Comment on lines +43 to 44
/** Unique id within the blueprints for the rundown */
_id: ExpectedPackageId
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file and understand the context better
cat -n packages/shared-lib/src/package-manager/package.ts | head -60

Repository: Sofie-Automation/sofie-core

Length of output: 2738


🏁 Script executed:

# Search for ExpectedPackage interface definition
rg -n "interface ExpectedPackage|type ExpectedPackage" packages/shared-lib/src/package-manager/ -A 20

Repository: Sofie-Automation/sofie-core

Length of output: 8577


🏁 Script executed:

# Search for how _id is being generated
rg -n "_id\s*[:=]" packages/shared-lib/src/package-manager/ -B 2 -A 2

Repository: Sofie-Automation/sofie-core

Length of output: 3142


🏁 Script executed:

# Search for _id usage in the broader codebase
rg -n "expectedPackage\._id|_id.*expected" --type=ts -B 2 -A 2 | head -100

Repository: Sofie-Automation/sofie-core

Length of output: 9800


🏁 Script executed:

# Look for any package generation logic
rg -n "ExpectedPackageId|packageId" packages/shared-lib/src/package-manager/ -B 1 -A 1 | head -50

Repository: Sofie-Automation/sofie-core

Length of output: 2217


🏁 Script executed:

# Check the old documentation comment in publications.ts
cat -n packages/shared-lib/src/package-manager/publications.ts | sed -n '28,40p'

Repository: Sofie-Automation/sofie-core

Length of output: 657


🏁 Script executed:

# Search for any other references to the old documentation text
rg -n "Unique id of the expectedPackage" --type=ts

Repository: Sofie-Automation/sofie-core

Length of output: 170


🏁 Script executed:

# Look at how expectedPackage._id is actually generated/created
rg -n "expectedPackage._id\s*=" --type=ts -B 3 -A 1 | head -60

Repository: Sofie-Automation/sofie-core

Length of output: 483


🏁 Script executed:

# Search for where getExpectedPackageId is defined to understand the scoping
rg -n "function getExpectedPackageId|const getExpectedPackageId|export.*getExpectedPackageId" --type=ts -A 10 | head -40

Repository: Sofie-Automation/sofie-core

Length of output: 1294


🏁 Script executed:

# Check if there are any comments about uniqueness scope elsewhere in the codebase
rg -n "Unique id.*blueprint|Unique id.*rundown" --type=ts -B 2 -A 2

Repository: Sofie-Automation/sofie-core

Length of output: 3230


🏁 Script executed:

# Check if PackageManagerExpectedPackage._id and ExpectedPackage.Base._id are related or if they should have matching docs
cat -n packages/shared-lib/src/package-manager/publications.ts | sed -n '28,42p'

Repository: Sofie-Automation/sofie-core

Length of output: 734


🏁 Script executed:

# See if there are any PR notes about the documentation change
git log --oneline --all | grep -i "package\|blueprint\|document" | head -20

Repository: Sofie-Automation/sofie-core

Length of output: 177


Clarify the scoping mechanism in the _id documentation.

The updated documentation "Unique id within the blueprints for the rundown" is accurate but could be more explicit. Consider clarifying that _id is scoped per parent context (rundown, studio, or bucket), as indicated by the getExpectedPackageId() function which takes a parentId parameter. A more precise wording would help prevent misunderstandings when integrating or extending this package sharing logic:

Suggested wording: "Unique id scoped to the parent (rundown, studio, or bucket) for which this package is defined"

This is especially important since the PR implements package sharing across ingest and playout contexts where ID scoping semantics directly affect how packages are deduplicated and matched.

🤖 Prompt for AI Agents
In `@packages/shared-lib/src/package-manager/package.ts` around lines 43 - 44,
Update the _id JSDoc to explicitly state its parent scoping: change the comment
for _id (ExpectedPackageId) to indicate it is a unique identifier scoped to the
parent context (rundown, studio, or bucket) for which the package is defined,
referencing the getExpectedPackageId(parentId) behavior so integrators know
scoping affects deduplication and matching across ingest/playout.

Comment on lines +40 to +41
/** Lower should be done first. If not set, the priority is considered as low priority */
priority: number | null
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for usages of PackageManagerExpectedPackage across the codebase
echo "=== Searching for PackageManagerExpectedPackage usages ==="
rg -n --type=ts 'PackageManagerExpectedPackage' | head -30

echo ""
echo "=== Searching for priority field access and usage ==="
rg -n --type=ts -B2 -A2 '\.priority' | grep -E '(priority|sort|compar|<|>|===|!==)' | head -40

echo ""
echo "=== Checking for null checks related to priority ==="
rg -n --type=ts 'priority\s*\?' | head -20

echo ""
echo "=== Searching priority in sorting context ==="
rg -n --type=ts 'priority.*sort|sort.*priority' | head -20

Repository: Sofie-Automation/sofie-core

Length of output: 5688


🏁 Script executed:

#!/bin/bash
# Look at the actual file to understand the change
echo "=== Contents of publications.ts around lines 40-41 ==="
cat -n packages/shared-lib/src/package-manager/publications.ts | sed -n '30,50p'

echo ""
echo "=== Full type definition of PackageManagerExpectedPackage ==="
rg -n --type=ts -B20 'priority: number' packages/shared-lib/src/package-manager/publications.ts | head -60

Repository: Sofie-Automation/sofie-core

Length of output: 1570


🏁 Script executed:

#!/bin/bash
# Check the context of the sorting in rundown.ts
echo "=== Context around rundown.ts:645 ==="
cat -n packages/webui/src/client/lib/rundown.ts | sed -n '638,655p'

echo ""
echo "=== Check type of 'instance' variable ==="
cat -n packages/webui/src/client/lib/rundown.ts | sed -n '630,650p'

echo ""
echo "=== Check imports in rundown.ts to see if it uses PackageManagerExpectedPackage ==="
rg -n 'import|PackageManager' packages/webui/src/client/lib/rundown.ts | head -30

Repository: Sofie-Automation/sofie-core

Length of output: 4788


🏁 Script executed:

#!/bin/bash
# Check the publication.ts file where priority is compared
echo "=== Context around publication.ts:223 ==="
cat -n meteor/server/publications/packageManager/expectedPackages/publication.ts | sed -n '215,235p'

echo ""
echo "=== Check the webui PackageWorkStatus to see how it handles priority ==="
cat -n packages/webui/src/client/ui/Status/package-status/PackageWorkStatus.tsx | sed -n '140,160p'

Repository: Sofie-Automation/sofie-core

Length of output: 1495


Handle nullable priority in UI display and ensure all comparisons account for null values.

The priority field is now number | null, which requires null handling in consumers. The main concern is in PackageWorkStatus.tsx:149 where priority is displayed directly without checking for null—if priority is null, this renders "null" as a string in the UI. Additionally, any arithmetic operations or sorting logic involving priority must use null-coalescing (??) or explicit null checks to avoid NaN results. The equality check in publication.ts:223 is safe, but code performing numeric operations on priority should follow the pattern used in timeline/generate.ts:572 (o.priority ?? 0).

Comment on lines +59 to +61
.finally(() => {
reloadingNow.current = null
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Race condition in finally block can discard valid responses.

When switching devices quickly, if Device A's request completes after Device B's request has started, Device A's finally block will set reloadingNow.current = null. This causes Device B's response to be incorrectly discarded because the check reloadingNow.current !== deviceId at lines 38 and 43 will evaluate to true (null !== deviceId_B).

Proposed fix
 				.finally(() => {
-					reloadingNow.current = null
+					if (reloadingNow.current === deviceId) {
+						reloadingNow.current = null
+					}
 				})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.finally(() => {
reloadingNow.current = null
})
.finally(() => {
if (reloadingNow.current === deviceId) {
reloadingNow.current = null
}
})
🤖 Prompt for AI Agents
In `@packages/webui/src/client/ui/Settings/DevicePackageManagerSettings.tsx`
around lines 59 - 61, The finally block unconditionally clears
reloadingNow.current which can race with concurrent requests; change the finally
to only set reloadingNow.current = null if it still equals the deviceId for that
request (i.e., in the finally check if reloadingNow.current === deviceId then
set to null) so a later-started request doesn’t get invalidated by an earlier
completion; locate the finally that currently does reloadingNow.current = null
and add this conditional guard around that assignment.

return keys.map(({ packageId }) => {
const p = packagesWithWorkStatuses[packageId]

console.log('p', p)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug console.log statements before merging.

Lines 149 and 163 contain debug logging that should be removed. These will clutter the browser console in production and may expose internal data structures.

🧹 Proposed fix
 		return keys.map(({ packageId }) => {
 			const p = packagesWithWorkStatuses[packageId]
-
-			console.log('p', p)

 			return p.package ? (
 	function renderPackageContainerStatuses() {
 		return packageContainerStatuses.map((packageContainerStatus) => {
 			const device = peripheralDevicesMap.get(packageContainerStatus.deviceId)
-			console.log(device, packageContainerStatus.deviceId)
 			return (

Also applies to: 163-163

🤖 Prompt for AI Agents
In `@packages/webui/src/client/ui/Status/package-status/index.tsx` at line 149,
Remove the debug console.log calls in the package-status module: delete the
console.log('p', p) call (and the other console.log found later in the same
component) so no debug logging is emitted from that component in production;
after removing them, run lint/format and a dev build/test to ensure no leftover
debug statements or unused variables remain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Contribution from BBC Contributions sponsored by BBC (bbc.co.uk)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants