npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@plasius/entity-manager

v1.2.4

Published

Entity definition & validation helpers for Plasius ecosystem

Downloads

535

Readme

@plasius/entity-manager

npm version Build Status coverage License Code of Conduct Security Policy Changelog

Entity definitions and validation schemas for the Plasius ecosystem.

This package is part of the Plasius LTD selective open-source strategy. For more on our approach, see ADR-0013: Selective Open Source. This package is maintained as open source to foster community trust and enable integration, while the core Plasius platform remains proprietary.

Apache-2.0. ESM + CJS builds. TypeScript types included.


Installation

npm install @plasius/entity-manager

Usage

import {
  userEntitySchema,
  PreferredDisplayOrder,
  UserNameStatus,
} from "@plasius/entity-manager";

const user = {
  type: "userEntity",
  version: "1.0",
  email: "[email protected]",
  name: {
    firstName: "Alice",
    lastName: "Lovelace",
    displayName: "Alice L.",
    status: UserNameStatus.COMPLETE,
    preferredDisplayOrder: PreferredDisplayOrder.DISPLAY_NAME,
  },
};

const result = userEntitySchema.validate(user);
if (!result.valid) {
  console.error(result.errors);
}

UserName.status is optional for backwards compatibility. An omitted status is treated as complete. Identity/profile services can set UserNameStatus.INCOMPLETE when schema-safe provider placeholders are awaiting user review. This is optional in both runtime validation and the exported TypeScript interface. Display names use the dedicated shared display-name validator and may contain Unicode decimal digits; first, middle, and last names retain the stricter personal-name validator.

Editable Profile Validation Translations

Editable profile validation issues expose stable field and message keys with en-GB defaults resolved through @plasius/translations.

import {
  editableUserProfileValidationTranslationKeys,
  mapEditableUserProfileValidationErrors,
  translateEditableUserProfileValidationText,
  validateEditableUserProfile,
} from "@plasius/entity-manager";

const validation = validateEditableUserProfile(profile);
const mapped = mapEditableUserProfileValidationErrors(validation);

const message = translateEditableUserProfileValidationText(
  editableUserProfileValidationTranslationKeys.required,
  { field: "First name" },
);

console.log(mapped.issues[0]?.fieldKey, mapped.issues[0]?.messageKey, message);

Export Overview

Base entity

  • baseEntitySchema, baseEntityShape, BaseEntity
  • Required fields include partitionKey, id, entityType, createdAt, createdBy, and isDeleted (plus system type and version).
  • Persistence-only fields such as partitionKey, createdBy, updatedBy, deletedBy, and deletedReason are marked internal and are omitted by default when calling schema.serialize(...).

Privacy-safe feedback entities

  • Actor-free content metadata: systemManagedFeedbackPacketEntitySchema, systemManagedFeedbackDraftEntitySchema, systemManagedFeedbackReportEntitySchema, systemManagedFeedbackCheckpointEntitySchema, and systemManagedFeedbackReconstructionEntitySchema
  • Identifier-free reporting source: feedbackBugHealthMetricsCounterEntitySchema and the closed feedbackBugHealthMetricsAbuseBlockCountsShape
  • Isolated reporter controls: feedbackProgressiveCooldownAggregateEntitySchema, feedbackCommitReconciliationOutboxEntitySchema, feedbackCommittedAcceptanceDeliveryOutboxEntitySchema, and feedbackReviewEligibilityEntitySchema; plus the deprecated compatibility projections feedbackAbuseControlEntitySchema and feedbackSubmissionReservationEntitySchema
  • Closed constants: FeedbackArtifactKind, FeedbackProcessor, FeedbackSubmissionKind, FeedbackReservationState, FEEDBACK_PROGRESSIVE_COOLDOWN_LADDER_MS, FEEDBACK_PROGRESSIVE_COOLDOWN_RESERVATION_LEASE_MS, FEEDBACK_PROGRESSIVE_COOLDOWN_RECONCILIATION_MS, FEEDBACK_PROGRESSIVE_COOLDOWN_PURGE_SAFETY_MS, FEEDBACK_PROGRESSIVE_COOLDOWN_RESET_MS, FEEDBACK_PROGRESSIVE_COOLDOWN_MAX_RESERVATIONS, and FEEDBACK_COMMITTED_ACCEPTANCE_DELIVERY_GRACE_MS, FEEDBACK_COMMITTED_ACCEPTANCE_DELIVERY_PURGE_SAFETY_MS, and FEEDBACK_REVIEW_DENY_SECONDS; and the exact FEEDBACK_DRAFT_TTL_SECONDS draft lifetime

Feedback content metadata deliberately does not extend BaseEntity: system artifacts have no createdBy, updatedBy, or deletedBy identity. Reporter controls accept only a purpose/version-scoped HMAC token in the form fbs1.<43 canonical unpadded base64url characters>. Reservation IDs use a canonical 22-character encoding of 128 random bits. Validators reject non-zero unused pad bits, so one binary token cannot have multiple textual aliases. Raw account subjects, narrative, pixels, network metadata, and arbitrary extra fields are rejected.

Hourly bug-health source counters are kept in the distinct feedbackMetricsControl table. Sixteen deterministic shards per canonical UTC hour contain only terminal application-attempt totals, rejected totals, fixed progressive-cooldown/fail-closed bands, shard-zero minute heartbeat slots, and system lifecycle/CAS state. There is no reporter, pseudonym, packet join, request metadata, URL, route, narrative, client timestamp, or exact event timestamp. The application-owned source deliberately has no edge-blocked field and must never derive one from raw WAF or access logs.

One conditional revision records exactly one terminal outcome, one new contiguous heartbeat slot, or one terminal finalisation. Shard zero must carry all 60 heartbeat slots before any exact hour is admissible. Finalisation is at least two minutes after the hour ends, the live correction source expires nine days after the hour, and bounded backup deletion is due one day later. A site adapter must combine schema validation with ETags/transactions, read all 16 exact row IDs, and fail a missing or partial hour closed; absence is never an all-zero report. See ADR-0009 and the counter design.

Adapters validate existing provider rows with validateFeedbackBugHealthMetricsCounterSnapshot. That read-only result does not authorise persistence. feedbackBugHealthMetricsCounterEntitySchema rejects exact replay writes; an identical retry must be acknowledged without a replace, upsert, touch, or provider TTL refresh.

Every terminal counter mutation must atomically create a server-random feedbackBugHealthMetricsOperationReceiptEntity in the same counterId partition. The receipt binds the counter revision and one closed outcome for 15 minutes, carries no reporter or request data, and cannot be updated or replayed as a write. After an ambiguous provider response, adapters read the exact receipt: its presence proves the whole transactional batch committed; absence permits a fresh attempt. Receipts must use create-only semantics and are due for hard deletion and bounded-backup expiry within one further day.

All reporter-control fields are internal, so default serialization exposes no pseudonym, reservation, counter, or expiry. The aggregate state ID and its complete state are also redacted by log sanitization. Reservation and reconciliation state have no packet, artifact, or draft ID and therefore cannot create a durable join from the pseudonymous control plane to identifier-free content.

Structured drafts have a separate actor-free metadata entity in feedbackDrafts. It fixes every dirty-save lifetime to 24 hours, requires an ETag/CAS revision increment, and rejects narrative, ciphertext, reporter keys, final packet IDs, and arbitrary fields. An adapter must validate the draft packet itself with the @plasius/schema draft contract and compose payload plus entity metadata in one conditional storage operation; this entity does not redeclare or weaken that payload contract. Draft expiry is a hard-delete deadline, not permission to extend retention through soft delete or backups.

The progressive bug controller is persisted as one authoritative row per canonical fbs1.* state ID. Its nested state is wire-equivalent to the @plasius/api reservation-v1 ProgressiveCooldownState, including reservation-time acceptance anchoring and owner-bound attemptGeneration/attemptTokenDigest authority and the writing state; the envelope adds only the row ID, numeric CAS revision, server write epoch, and deletion TTL. Validate updates with the current row:

const validation =
  feedbackProgressiveCooldownAggregateEntitySchema.validate(next, current);

The next revision must be exactly current.revision + 1. Persistence must also use an ETag or transactional condition; schema validation is not a substitute for an atomic storage write.

The aggregate enforces unique canonical reservation IDs, idempotency digests, and attempt-token digests, at most 64 retained records, one active lease, the exact five-minute lease, the 5m → 15m → 1h → 6h → 24h ladder, a 48-hour quiet reset, and seven days as the absolute post-expiry privacy deadline. New reservations carry generation-one one-use write authority. Only the matching reserved record can enter writing, and an admitted write cannot be released; it converges through immutable-acceptance commit/reconciliation. The first six days are the live reconciliation interval; the final 24 hours are available only for verified purge and expiry of bounded backups. A released reservation may become committed during its reconciliation interval after immutable acceptance is independently verified; this restarts the cooldown without creating a packet/content join. Exact cloned replays are accepted without advancing the CAS revision, but neither the submitted nor stored row may contain unknown fields.

feedbackCommitReconciliationOutboxEntitySchema is an immutable control-plane outbox row created in the same control transaction as reserved → writing. It contains only the keyed state ID, random reservation ID, closed submission kind, timing bounds, TTL, and revision. It intentionally excludes packet, artifact, draft, idempotency, raw/digested attempt authority, narrative, and account fields. Reconciliation deletes the row after a deterministic outcome; live TTL ends at the six-day reconciliation cutoff and the absolute purge deadline is exactly one day later.

feedbackCommittedAcceptanceDeliveryOutboxEntitySchema is a second, purpose-specific immutable control row created atomically with the successful control commit. It carries only the canonical keyed state ID, reservation ID, closed packet kind, separate server acceptance and control-commit epochs, delivery/deletion bounds, TTL, and revision zero. It contains no packet ID, accepted content/time string, Blob locator/hash, idempotency or attempt-authority value, request metadata, narrative, ciphertext, or pixels. The trusted delivery worker derives the packet ID transiently, verifies the immutable packet, writes the separate identifier-free acceptance evidence, and only then conditionally deletes this row.

The acceptance epoch is the server reservation time verified against the immutable packet; eligibility is anchored to it while live TTL is shortened from the later control commit. For a bug row, the eligibility interval is exactly one step from the closed 5m → 15m → 1h → 6h → 24h ladder; for a review it is exactly 30 days. Live delivery remains available for six further days and the final day is reserved for explicit deletion, absence verification, and bounded backup expiry. Thus all copies are gone no later than seven days after the associated cooldown or review deny expires. The schema cannot make the cross-store operation atomic; the host must create this row in the same Cosmos partition transaction as the control commit and must use immutable/idempotent evidence writes before its ETag-conditional deletion.

state.hardDeleteByMs is the absolute upper-bound deletion deadline and is recomputed as the latest record or active cooldown/reset reconciliation horizon plus exactly 24 hours. Each record's reconciliationUntilMs is its six-day availability cutoff. ttlSeconds is the maximum whole-second TTL budget ending at the latest reconciliation horizon, one full day before the hard-delete deadline. A zero budget is valid only on an empty, inactive delete instruction; an active or partially retained aggregate must have a positive budget.

The value is calculated at writtenAtMs; it must not be copied directly into Cosmos ttl, whose clock starts at the database _ts. At persistence time an adapter must shorten the database TTL using trusted current time, issue a delete instead when no positive duration remains, and never persist Cosmos TTL zero (where zero is invalid). A purge worker must explicitly delete and verify the row during the safety window. Soft-delete, versions, and backups must be configured to be absent by hardDeleteByMs; a database TTL alone is insufficient. This compensates for Cosmos TTL being relative to the last database modification and for physical deletion being an asynchronous, capacity-dependent background task (Azure TTL behaviour). The isolated feedback-control boundary therefore requires a restore horizon of no more than 24 hours and must not be copied into longer-lived continuous or long-term backups.

The earlier per-subject abuse and per-reservation schemas remain exported only for source-compatible migration. They cannot atomically enforce aggregate capacity or released-to-committed reconciliation and must not be used for new writes. Review eligibility remains a separate 30-day overlay and is never folded into the bug aggregate. Its validator accepts an exact clone of the persisted row at the current non-zero revision for idempotent reads/retries; new rows must still start at revision zero, and every material update must advance by exactly one after the previous deny expires.

This release requires the registry-published @plasius/schema 1.4.0 or later for schema-owned feedback constants and closed draft contracts. Source, Git, workspace, and file dependency pins are not supported.

Other pseudonymous control entities use the same 24-hour purge safety window; identifier-free artifacts and checkpoints retain an exact whole-second lifecycle budget. A control hard-delete deadline must be between one and seven days after logical expiry so the safety budget never removes an effective cooldown or eligibility overlay. Writers must update the timestamp, TTL budget, and revision atomically. See ADR-0008 and the feedback entity boundary design.

The review contract validates an exact 30-day deny. The deprecated abuse and per-reservation projections retain their previous validation behaviour only for migration compatibility.

Report/checkpoint windows use closed purpose-specific UTC keys: hour:YYYY-MM-DDTHH, day:YYYY-MM-DD, or a five-minute reconcile:YYYY-MM-DDTHH:mm bucket. Checkpoint IDs are exactly checkpoint:<processor>:<windowKey>. Account-shaped strings, pseudonyms, UUIDs, invalid calendar values, and processor/window mismatches are rejected.

User and permissions

  • userEntitySchema, userNameSchema, userAvatarSchema
  • Editable profile validation helpers and translation keys: validateEditableUserProfile, mapEditableUserProfileValidationErrors, editableUserProfileFieldTranslationKeys, editableUserProfileValidationTranslationKeys, entityManagerEnGbTranslations, translateEditableUserProfileValidationText
  • settingsEntitySchema, permissionsEntitySchema, featureFlagEntitySchema, roleEntitySchema
  • Enums: PreferredDisplayOrder, UserNameStatus, UserEmailPreferences, UserNotificationPreferences, Role, Scope

Consumers that only need the permission scope contract should use the registration-free package subpath:

import { Scope } from "@plasius/entity-manager/permissions";

const requiredScope = Scope.VIEW;

The subpath provides ESM, CommonJS, and TypeScript declaration outputs without loading @plasius/schema or registering entity schemas. The existing Scope export from @plasius/entity-manager remains available and has the same enum values.

Administrative governance

  • Platform security: platformAuthorityAssignmentSchema, PlatformAuthority, PlatformAuthorityAssignmentStatus, getLegacyPlatformAuthorityPromotions
  • Groups: groupDefinitionSchema, groupMembershipSchema, groupMembershipBoundarySchema, GroupMembershipRole, validateGroupMembershipBoundary
  • Professional standing: professionalRoleCategorySchema, professionalRoleDefinitionSchema, professionalRoleAssignmentSchema, BUILT_IN_PROFESSIONAL_ROLE_CATEGORIES, isProfessionalInterfaceKey

Platform authority, group ownership, and professional standing are deliberately separate contracts. The legacy Role and RoleEntity exports remain unchanged. Only admin and service-admin are migration candidates for platform-owner; user-admins and moderators are not promoted.

import {
  PlatformAuthority,
  PlatformAuthorityAssignmentSource,
  PlatformAuthorityAssignmentStatus,
  platformAuthorityAssignmentSchema,
} from "@plasius/entity-manager";

const result = platformAuthorityAssignmentSchema.validate({
  type: "platformAuthorityAssignment",
  version: "1.0.0",
  assignmentId: "authority-assignment-001",
  identity: {
    issuer: "https://identity.example.test",
    subject: "provider-subject-001",
  },
  accountId: "account-owner-001",
  authority: PlatformAuthority.PLATFORM_OWNER,
  status: PlatformAuthorityAssignmentStatus.ACTIVE,
  source: PlatformAuthorityAssignmentSource.LEGACY_ADMIN_MIGRATION,
  revision: 1,
  lastMutationId: "migration-run-001",
  assignedAt: "2026-07-18T09:00:00.000Z",
  assignedByAccountId: "account-admin-001",
  reason: "Promote an existing full administrator.",
});

Group and professional-role writes carry a positive revision plus an internal lastMutationId for optimistic concurrency and idempotency. Validate the complete proposed GroupMembershipBoundary in the same storage transaction to prevent removal of the final active owner.

Professional assignments require world, character, and institution scope. An optional group identifies a delegated governance boundary. Future interface and authority namespace keys must start with game.professional.; these keys never imply a platform permission or Admin capability. Audit actor IDs, OIDC subjects, mutation identifiers, and protected reasons are omitted by default public serialization.

Assets

  • assetEntitySchema, imageAssetEntitySchema, audioAssetEntitySchema, modelAssetEntitySchema, objectAssetEntitySchema

objectAssetEntitySchema includes object-specific fields for payload URL, optional thumbnail, optional format/size, and component wiring.

  • Enums: AudioChannel, ModelAssetFormat

Components

  • baseComponentSchema, physicsComponentSchema, animationComponentSchema, shadowComponentSchema, levelOfDetailComponentSchema
  • Enum: ComponentTypes

Auth and translations

  • authenticatedUserSchema, AuthProvider
  • Family identity contracts: ageAssuranceEvidenceSchema, managedChildProfileSchema, actorSubjectPrincipalSchema, householdIdentitySchema, householdGuardianBoundarySchema, guardianRoleAssignmentSchema, guardianRelationshipSchema, and guardianInvitationSchema
  • Family identity runtime constants and corresponding literal-union types: AgeBand, AgeAssuranceLevel, AgeAssuranceMethod, PrincipalAccountType, PrincipalType, and GuardianRoleAssignmentKind
  • Family identity enums: ManagedChildLifecycleState, GuardianRole, GuardianRoleAssignmentStatus, GuardianRelationshipStatus, GuardianInvitationKind, and GuardianInvitationStatus
  • Family identity validators: validateAgeAssuranceEvidence, validatePublicAgeAssuranceEvidence, and validateHouseholdGuardianBoundary
  • translatableSchema, supportedLanguagesSchema

Validators and utilities

  • isValidAzureTableKey, isValidEntityType, validateAssetSchema
  • validateFeatureFlagValue, validateSettingValue

Public Serialization

Entity schemas validate the full persisted entity shape, including internal audit and storage metadata. When returning data to clients, prefer schema.serialize(entity) so only public fields are included by default.

import { baseEntitySchema } from "@plasius/entity-manager";

const payload = baseEntitySchema.serialize({
  type: "baseEntity",
  version: "1.0.0",
  entityType: "baseEntity",
  partitionKey: "tenant-a",
  id: "row-1",
  createdAt: new Date().toISOString(),
  createdBy: "user-1",
  isDeleted: false,
});

// partitionKey and createdBy are omitted from the serialized payload.
console.log(payload);

Family identity and delegated sessions

Managed-child profiles are separate from UserEntity. They have their own stable account ID and display name, but deliberately do not contain an email, exact date of birth, guardian contact data, payment data, or Token balances. The existing UserEntity email requirement is unchanged.

Age assurance is represented using a derived AgeBand plus minimal assurance evidence. The optional evidenceRef is marked internal and is omitted by default serialization. ageAssuranceEvidenceSchema and validateAgeAssuranceEvidence remain strict stored-evidence boundaries and require that reference for provider verification. Public actor/subject principals use validatePublicAgeAssuranceEvidence, which permits only the intentional absence of that protected reference so serialized principals can be validated again without weakening stored evidence.

An external account provider's positive adult category is represented separately as provider-asserted / provider-age-signal. It is valid only on an 18+ self principal, is never accepted as managed-child assurance, and does not mean verified. Consumers must continue to require verified for funding, reward providers, or any other high-assurance policy.

import {
  AgeAssuranceLevel,
  AgeAssuranceMethod,
  AgeBand,
  ManagedChildLifecycleState,
  managedChildProfileSchema,
} from "@plasius/entity-manager";

const child = managedChildProfileSchema.validate({
  type: "managedChildProfile",
  version: "1.0.0",
  accountId: "managed-child-001",
  displayName: "Moon Explorer",
  ageBand: AgeBand.SIX_TO_NINE,
  assurance: {
    level: AgeAssuranceLevel.GUARDIAN_ATTESTED,
    method: AgeAssuranceMethod.GUARDIAN_ATTESTATION,
    assertedAt: "2025-07-15T09:00:00.000Z",
  },
  lifecycleState: ManagedChildLifecycleState.ACTIVE,
  createdAt: "2025-07-15T09:00:00.000Z",
  createdByAccountId: "guardian-account-001",
});

The creating guardian must differ from the managed-child account. Assurance must have been asserted and remain unexpired at createdAt. A closed profile may retain optional claim history only for an adult account and in the order createdAt ≤ claimedAt ≤ closedAt.

Delegated sessions retain both identities. The guardian remains actor; the managed child is subject. The relationship and authorization version bind the session to current family authority. Guardian roles are intentionally not part of the child principal and must not be inherited into child authorization. AuthenticatedUser may carry this principal in its optional principal field. Legacy sessions remain valid without it. When present, the active subject account must equal AuthenticatedUser.sub; delegated child mode therefore uses the managed-child account as sub and retains the guardian only as the audit actor.

import {
  AgeAssuranceLevel,
  AgeAssuranceMethod,
  AgeBand,
  PrincipalAccountType,
  PrincipalType,
  actorSubjectPrincipalSchema,
} from "@plasius/entity-manager";

const principal = actorSubjectPrincipalSchema.validate({
  type: "actorSubjectPrincipal",
  version: "1.0.0",
  actor: {
    accountId: "guardian-account-001",
    accountType: PrincipalAccountType.USER,
  },
  subject: {
    accountId: "managed-child-001",
    accountType: PrincipalAccountType.MANAGED_CHILD,
  },
  principalType: PrincipalType.GUARDIAN_DELEGATED,
  relationshipId: "guardian-relationship-001",
  authorizationVersion: 0,
  ageBand: AgeBand.SIX_TO_NINE,
  assurance: {
    level: AgeAssuranceLevel.GUARDIAN_ATTESTED,
    method: AgeAssuranceMethod.GUARDIAN_ATTESTATION,
    assertedAt: "2025-07-15T09:00:00.000Z",
  },
  authenticatedAt: "2025-07-15T09:00:00.000Z",
});

Family timestamps use canonical UTC only: YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.sssZ. Calendar rollovers, offsets, and other fractional precision are rejected. Active principals reject future authentication, assurance asserted after authentication, and assurance that has already expired. Accepted child-link invitations require assurance valid at their resolution time.

Family opaque identifiers share the economy contract's ASCII grammar: one to 128 characters, starting with an alphanumeric character and continuing with alphanumerics, ., _, :, or -.

householdIdentitySchema names the current hostGuardianAccountId without introducing treasury or payment state. Role assignments distinguish host-guardian from co-guardian; a host assignment always has both child and finance management. Validate every proposed complete assignment snapshot with householdGuardianBoundarySchema in the same transaction. It requires exactly one active host matching the household identity, so revoking the last host is invalid unless a replacement becomes active atomically. Grant and revocation actor IDs remain protected audit fields.

The relationship and invitation schemas also validate explicit guardian roles, lifecycle state, expiry ordering, two authenticated approvals for acceptance, mandatory resolver audit for every terminal outcome, and safe authorization versions. Once both sides approve, only the initiating guardian or invitation target may complete acceptance; arbitrary third-party account IDs are rejected. APIs must still enforce guardian step-up authentication, atomic writes, and current relationship-version freshness.


Key Documentation


Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.


License

Licensed under the Apache-2.0 License.

Release integrity

CI keeps the administrative contributor registry outside Git and npm package artifacts using exact, case-normalised path checks. Pull requests run on GitHub-hosted runners after same-repository admission; protected main CI uses the workflow-restricted self-hosted group. Dependency caching is isolated under each CI job's runner-temporary directory, so exact-main validation never reads or uploads another repository's shared runner cache. Release preparation and npm publication use GitHub-hosted runners with Node.js 24.18.0 LTS.

Package releases start by dispatching cd.yml from main with the prepare phase. The workflow lands the version and changelog through a unique, non-force-pushed release pull request, waits for the push-triggered ci.yml run for that exact merge commit, and then dispatches a separate publish phase from the same main SHA. Preparation is serialized, while publication runs use the prepared SHA in their concurrency identity so the self-dispatched publication cannot be blocked by the preparation run that created it. Publication fails closed if the workflow SHA, remote main, successful CI evidence, package version, release tag, or version-derived prerelease identity differs. npm authentication uses the production environment's workflow-specific OIDC trusted-publisher binding; no long-lived npm write token or token fallback is used. The reusable preparation workflow receives only the release GitHub App private key rather than inherited organization secrets. Dependencies, validation, coverage upload, SBOM generation, and npm pack --ignore-scripts run in a separate read-only hosted job. The production OIDC job downloads the exact package and SBOM artifact IDs, verifies both file and GitHub artifact digests plus package identity, and publishes only the verified tarball with lifecycle scripts disabled. The optional Codecov step runs only after both immutable publication artifacts have been sealed. Before GitHub release finalization, the workflow requires npm's published SHA-512 integrity to match that exact tarball and the version-derived npm distribution tag to point at the exact package version; duplicate retries fail closed on either mismatch.

CD remains disabled until the npm binding for organization Plasius-LTD, repository entity-manager, workflow cd.yml, environment production, and allowed action npm publish has been independently verified. Rollback is to disable cd.yml; an interrupted unpublished release can be retried with prepare and bump: none after main is stable.