During PL-14 schema migration, we discovered that the @event-service-agent/contracts package has conflicting responsibilities:
Core Issue: Attempting to define MessageEnvelope with typed DomainMessage union exposed architectural problems:
```typescript ignore // contracts defines plain DTO shape import { Effect, Schema } from ‘effect’
export const DomainMessage = Schema.Union( Schema.Struct({ _tag: Schema.Literal(‘DueTimeReached’), tenantId: Schema.String, // ← Lost TenantId brand! }), )
Effect.gen(function* () { // Consumer can’t use decoded payload: const envelope = yield* MessageEnvelope.decodeJson(json) yield* workflow.handle(envelope.payload) // ^^^^^^^^^^^^^^^^ // ❌ TYPE MISMATCH: plain object vs branded types })
**Fatal DX flaw**: Decoded payload loses type information (brands, validation), defeating the purpose of Effect Schema.
### Option 2: Import schemas from modules (REJECTED)
```typescript ignore
// contracts imports from timer
import { DueTimeReached } from '@event-service-agent/timer/domain'
Circular dependency: Timer already imports contracts for TenantId → contracts imports timer → circular.
Violates architecture: Contracts (shared kernel) should not depend on modules (concrete implementations).
Split contracts into two packages:
@event-service-agent/schemas - All Effect Schemas (Foundation)Purpose: Single source of truth for all Effect Schema definitions (foundational types + domain messages).
Contents:
TenantId, ServiceCallId, CorrelationId, EnvelopeId, Iso8601DateTime, UUID7DueTimeReached, ServiceCallScheduled, StartExecution, etc.MessageEnvelope with typed DomainMessage unionRequestSpecUUID7.makeUUID7(), TenantId.makeUUID7()UUID7 service implementationDependencies: effect only (self-contained)
Structure:
packages/schemas/
src/
shared/
uuid7.schema.ts
tenant-id.schema.ts
service-call-id.schema.ts
correlation-id.schema.ts
envelope-id.schema.ts
iso8601-datetime.schema.ts
uuid7.service.ts
messages/
timer/events.schema.ts
orchestration/
events.schema.ts
commands.schema.ts
execution/events.schema.ts
api/commands.schema.ts
envelope/
domain-message.schema.ts
message-envelope.schema.ts
http/
request-spec.schema.ts
@event-service-agent/platform - Infrastructure AbstractionsPurpose: Port interfaces, routing configuration, and infrastructure abstractions (hexagonal architecture boundaries).
Contents:
EventBusPort, UUID7PortPublishError, SubscribeError (Data.TaggedError)Topics namespaceDependencies: effect, @event-service-agent/schemas
Structure:
packages/platform/
src/
ports/
event-bus.port.ts
uuid.port.ts
routing/
topics.ts
effectProblem: Name confusion after splitting
Alternative considered: Keep “contracts” name with narrowed scope
effect (external)
↓
schemas (foundational + domain schemas)
↓
platform (ports + routing)
↓
modules (timer, orchestration, execution, api)
Key properties:
@event-service-agent/contracts → @event-service-agent/platform@event-service-agent/schemas (created)schemas/From contracts/src/types/:
shared.type.ts → schemas/src/shared/*.schema.ts (split by type)uuid7.type.ts → schemas/src/shared/uuid7.schema.tshttp.type.ts → schemas/src/http/request-spec.schema.tsmessage-envelope.schema.ts → schemas/src/envelope/message-envelope.schema.tsFrom contracts/src/services/:
uuid7.service.ts → schemas/src/uuid7.service.tsFrom timer/src/domain/:
events.domain.ts → schemas/src/messages/timer/events.schema.tsplatform/contracts/src/ports/ → platform/src/ports/contracts/src/routing/ → platform/src/routing/contracts/src/messages/messages.ts (deprecated interfaces, replaced by schemas)contracts/src/types/message-envelope.type.ts (old interface, replaced by schema)Before:
```typescript ignore import { TenantId } from ‘@event-service-agent/contracts/types’ import { EventBusPort } from ‘@event-service-agent/contracts/ports’ import { Topics } from ‘@event-service-agent/contracts/routing’
**After**:
```typescript ignore
import { TenantId } from '@event-service-agent/schemas/shared'
import { DueTimeReached } from '@event-service-agent/schemas/messages/timer'
import { MessageEnvelope } from '@event-service-agent/schemas/envelope'
import { EventBusPort } from '@event-service-agent/platform/ports'
import { Topics } from '@event-service-agent/platform/routing'
Before (timer/package.json):
{
"dependencies": {
"@event-service-agent/contracts": "workspace:*"
}
}
After:
{
"dependencies": {
"@event-service-agent/schemas": "workspace:*",
"@event-service-agent/platform": "workspace:*"
}
}
schemas packagedocs/design/ports.md: Update port locations (contracts → platform)docs/design/messages.md: Update schema locations (contracts → schemas)docs/design/hexagonal-architecture-layers.md: Update package referencesdocs/design/modules/*.md: Update import paths in examplesdocs/plan/kanban.md: Add migration tasksdocs/plan/plan.md: Update architecture section if applicablePhase 1: Create schemas package
packages/schemas/ with package.jsonPhase 2: Rename contracts → platform
contracts/ → platform/Phase 3: Update modules
Phase 4: Update documentation
Phase 5: Cleanup
typescript ignore
import type { DueTimeReached } from '@event-service-agent/timer/domain'
docs/design/hexagonal-architecture-layers.md (package references)