Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions openspec/specs/production-release/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# production-release

## Purpose

Defines the compatibility, verification, packaging, documentation, security,
and publication evidence required before effectmq is presented as
production-ready.

## Requirements

### Requirement: Compatibility matrix is tested

The release SHALL publish and continuously test its supported Node.js, Redis,
Effect, RESP, and Redis-client versions and topologies.

#### Scenario: Supported matrix entry
- **WHEN** a version combination is listed as supported
- **THEN** its build, consumer typecheck, integration suite, and fault smoke tests pass in CI

### Requirement: Candidate uses the current Effect beta baseline

Immediately before cutting the candidate, the release SHALL resolve npm's
`beta` dist-tag for `effect` and every direct `@effect/*` dependency, record the
exact resolved versions, update the peer minimum and development dependencies,
and rerun all release gates. A newer `rc` dist-tag SHALL NOT replace the beta
baseline unless the release plan is explicitly changed.

#### Scenario: Effect beta has advanced
- **WHEN** npm's `beta` dist-tag differs from the versions in the candidate manifest
- **THEN** the manifest, lockfile, documentation, and packed-consumer fixture are updated to the resolved beta versions
- **AND** the full correctness, compatibility, package, benchmark, and soak gates pass again

### Requirement: Correctness and fault gates pass

Release CI SHALL pass reference-state-model/property tests, crash and
lease-loss tests, Redis restart and Sentinel failover tests, script-cache-loss
tests, clock-skew tests, and mixed-version upgrade/rollback tests.

#### Scenario: Release commit is published
- **WHEN** a package version is eligible for publication
- **THEN** every required correctness and fault job has succeeded for that exact commit

### Requirement: Performance bounds are published

The release SHALL publish reproducible throughput and p50/p95/p99 latency
measurements across documented backlog, payload, concurrency, and expiry-sweep
sizes, including the maximum configured atomic batch.

#### Scenario: Adversarial backlog benchmark
- **WHEN** the benchmark runs with a large due-task and expired-lease backlog
- **THEN** no single atomic queue operation exceeds the documented work bound

### Requirement: Packed package is consumer-tested

CI SHALL build and pack the package, assert the intended file list and
generated-artifact drift, and install the tarball into representative ESM
consumer projects that exercise every public export.

#### Scenario: Experimental source is present in the tarball
- **WHEN** the packed file list includes scratchpad, test, or unintended generated declarations
- **THEN** the release gate fails

### Requirement: Release documentation is complete

The release SHALL include accurate delivery guarantees, task-relationship
language, supported topology and compatibility tables, upgrade/rollback and
operations guides, security policy, contribution guide, and pre-1.0
compatibility policy.

#### Scenario: Exactly-once wording remains
- **WHEN** release documentation validation finds an exactly-once handler-execution claim
- **THEN** the release gate fails

### Requirement: Publication is provenance-bearing and gated

Publishing SHALL depend on the successful release gate for the exact commit and
SHALL use npm trusted publishing with generated provenance rather than a
long-lived publication token.

#### Scenario: Release gate has not passed
- **WHEN** the publication workflow runs for a commit without a successful release gate
- **THEN** no npm package is published
69 changes: 69 additions & 0 deletions openspec/specs/redis-operations/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# redis-operations

## Purpose

Defines the supported Redis deployment contract and operational bounds needed
to run the queue safely through restarts, failover, backlog spikes, and
retention cleanup.

## Requirements

### Requirement: Supported Redis topologies are explicit

The release SHALL support standalone Redis and Sentinel-managed non-sharded
primary/replica deployments. Redis Cluster SHALL be documented as unsupported.

#### Scenario: Cluster configuration is supplied
- **WHEN** a user attempts to configure Redis Cluster
- **THEN** startup fails with an unsupported-topology error before processing tasks

### Requirement: Application scripts recover after cache loss

Queue scripts SHALL be content-addressed, loaded into the application script
cache, invoked by digest, and transparently reloaded once when Redis reports a
missing script.

#### Scenario: Redis script cache is flushed
- **WHEN** the script cache is cleared between queue operations
- **THEN** the next operation reloads the expected script and retries without duplicating the transition

### Requirement: Mixed application versions do not replace each other's code

Starting one library version SHALL NOT globally replace the Redis-side
implementation used by another running version.

#### Scenario: Rolling application upgrade
- **WHEN** old and new workers start in either order against the same supported Redis deployment
- **THEN** each invokes the script version matching its own storage protocol

### Requirement: Maintenance work is bounded

Lease recovery, delayed promotion, retention trimming, relationship release,
and list inspection SHALL process configurable bounded batches and SHALL expose
a continuation until work is complete.

#### Scenario: Thousands of leases expire together
- **WHEN** more leases expire than the configured sweep batch size
- **THEN** one atomic operation processes at most the configured batch
- **AND** remaining work stays discoverable for subsequent sweeps

### Requirement: Retention is configurable and observable

Task records, terminal results, dead-letter entries, and lifecycle events SHALL
have configurable age/count retention. Health and metrics SHALL expose queue
depth, oldest age, sweep lag, Redis errors, and retention failures.

#### Scenario: Event stream reaches its configured limit
- **WHEN** appending an event would exceed the configured retention window
- **THEN** old events are trimmed within the documented approximation
- **AND** consumers can determine the earliest resumable cursor

### Requirement: Deployment safety requirements are documented

The production guide SHALL define persistence, `noeviction`, backup/restore,
replication data-loss windows, ACLs, TLS, timeouts, reconnection, graceful
shutdown, and indeterminate-write behavior.

#### Scenario: Producer loses connection after sending an offer
- **WHEN** the producer cannot determine whether Redis committed the offer
- **THEN** the API returns an indeterminate-write error that instructs retry with the same idempotency identity
44 changes: 44 additions & 0 deletions openspec/specs/scheduler-delivery/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# scheduler-delivery

## Purpose

Defines recurring scheduling as durable creation of idempotently identified
queue tasks so process failure cannot silently lose an already-consumed tick.

## Requirements

### Requirement: Every due tick maps to one task identity

A scheduled definition SHALL derive a deterministic task identity from the
schedule name and nominal tick time and SHALL offer that task idempotently.

#### Scenario: Two scheduler processes observe the same tick
- **WHEN** two processes attempt to materialize the same due tick
- **THEN** exactly one task generation exists for that schedule and tick identity

### Requirement: Tick execution uses queue delivery semantics

Scheduled handlers SHALL run through the managed queue worker and SHALL inherit
its at-least-once delivery, lease, retry, retention, and observability behavior.

#### Scenario: Scheduler process dies after offering
- **WHEN** the scheduler process dies after the tick task is stored but before a worker executes it
- **THEN** the task remains eligible for normal queue processing

### Requirement: Missed-tick policy is explicit

A schedule SHALL declare whether startup skips, coalesces, or backfills ticks
missed while no scheduler was running, including a maximum backfill count.

#### Scenario: Coalescing missed ticks
- **WHEN** a coalescing schedule restarts after multiple nominal ticks were missed
- **THEN** it creates one task representing the documented coalesced interval

### Requirement: Scheduler API does not claim exactly-once execution

Scheduler documentation SHALL distinguish idempotent tick creation from
at-least-once handler execution.

#### Scenario: Tick task is retried
- **WHEN** the worker loses its lease after beginning a scheduled handler
- **THEN** the same tick task may execute again under normal retry semantics
59 changes: 59 additions & 0 deletions openspec/specs/storage-protocol/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# storage-protocol

## Purpose

Defines a durable, lossless, versioned storage contract for typed task values
and metadata across compatible releases and rolling deployments.

## Requirements

### Requirement: User values round-trip losslessly

Payloads, successes, and typed failures SHALL remain opaque to Redis-side logic
and SHALL decode to the same supported value, including nested nulls, empty
arrays/maps, Unicode, binary values, and safe numeric values.

#### Scenario: Typed failure contains nested nulls
- **WHEN** a typed failure containing nested null values is stored and read
- **THEN** the decoded failure is deeply equal to the encoded failure

### Requirement: Stored records carry a protocol version

Every task generation and event record SHALL identify its storage protocol
version and task-schema identity. Readers SHALL reject unsupported versions
with a typed compatibility error rather than mis-decoding them.

#### Scenario: New reader encounters an unsupported record
- **WHEN** a reader encounters a record with an unsupported protocol version
- **THEN** it fails with an error that identifies the encountered and supported versions

### Requirement: Corruption is never normalized into valid empty data

Malformed bytes, invalid field types, and structurally invalid collections
SHALL fail decoding in the typed error channel. Only explicitly documented
canonical representations may normalize to an equivalent value.

#### Scenario: Error history decodes to a map
- **WHEN** the stored error-history field contains a non-list value
- **THEN** decoding fails instead of returning an empty history

### Requirement: Rolling compatibility is declared

Each release SHALL declare which protocol versions it can read and write. A
rolling upgrade SHALL either preserve a mutually readable write version or
require an explicit migration before mixed-version workers start.

#### Scenario: Mixed-version deployment
- **WHEN** old and new supported workers run concurrently
- **THEN** every record they exchange uses a protocol version readable by both

### Requirement: Encoded values are size bounded

The producer and worker APIs SHALL reject payloads, results, failures, error
histories, and relationship sets that exceed configured encoded-size or count
limits before an unbounded Redis operation occurs.

#### Scenario: Oversized result
- **WHEN** a handler returns a result larger than the configured maximum
- **THEN** acknowledgement fails with a typed size-limit error
- **AND** the task follows the configured terminal handling policy
66 changes: 47 additions & 19 deletions openspec/specs/task-completion-policies/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,60 @@ policy without consuming remaining handler retries.

### Requirement: onFailurePolicy placement

After handler retries are exhausted or skipped, the engine SHALL settle the
generation as failed. `delete` removes its record when no retention hold
prevents disposal; `mark-as-failure` indexes and retains it; `keep` retains it
outside terminal indexes. Active holds SHALL affect disposal only and SHALL NOT
hide terminal state.
After retries are exhausted or skipped, the engine SHALL settle the task as
failed and apply `onFailurePolicy`: `delete` removes the task from execution
indexes and deletes its record once no explicit result-retention holds remain;
`mark-as-failure` places it in the failed index and retains its record according
to configured retention; `keep` retains the record outside terminal indexes
according to configured retention. Active holds SHALL affect record disposal
only and SHALL NOT hide the terminal execution state.

#### Scenario: Marked failure with a hold
- **WHEN** a held task settles with `mark-as-failure`
- **THEN** it appears in the failed index immediately and remains readable
#### Scenario: Delete policy without holds
- **WHEN** a terminally failed task has delete policy and no result-retention holds
- **THEN** it appears in no execution index and its record is deleted

#### Scenario: Delete policy with a hold
- **WHEN** a terminally failed task has delete policy and an active result-retention hold
- **THEN** it is visibly terminal, appears in no runnable state, and its failure remains readable until the final hold releases

#### Scenario: Mark-as-failure policy with a hold
- **WHEN** a terminally failed task has mark-as-failure policy and an active hold
- **THEN** it appears in the failed index immediately and its record remains readable

#### Scenario: Keep failure policy
- **WHEN** a terminally failed task has keep policy
- **THEN** it appears in no execution index and its record is retained until configured expiry or administrative removal

### Requirement: onSuccessPolicy placement

On a valid fenced acknowledgement, `delete` removes the record when allowed,
`mark-as-success` indexes and retains it, and `keep` retains it outside terminal
indexes. Active holds SHALL affect disposal only.
On valid success acknowledgement, the engine SHALL settle the task as succeeded
and apply `onSuccessPolicy`: `delete` removes the task from execution indexes
and deletes its record once no explicit result-retention holds remain;
`mark-as-success` places it in the success index and retains its record according
to configured retention; `keep` retains the record outside terminal indexes
according to configured retention. Active holds SHALL affect record disposal
only and SHALL NOT hide the terminal execution state.

#### Scenario: Delete success with a hold
- **WHEN** a held task succeeds under delete policy
- **THEN** it is terminal and non-runnable while its result remains readable
- **WHEN** a succeeded task has delete policy and an active result-retention hold
- **THEN** its success remains readable until the final hold releases
- **AND** it is not runnable or hidden as an unfinished task

#### Scenario: Mark-as-success policy
- **WHEN** a succeeded task has mark-as-success policy
- **THEN** it appears in the success index immediately and remains according to configured retention

#### Scenario: Keep success policy
- **WHEN** a succeeded task has keep policy
- **THEN** it appears in no execution index and its record remains readable until configured expiry or administrative removal

### Requirement: Terminal indexes contain terminal tasks
### Requirement: Success and failed lists contain only dead tasks

Success and failed indexes SHALL contain only settled generations. A terminal
generation MAY have active retention holds; membership describes outcome and
does not imply immediate disposability.
The success and failed indexes SHALL contain only terminal tasks. A terminal
task MAY have active result-retention holds; index membership records outcome
and SHALL NOT imply that the task record is immediately disposable.

#### Scenario: Held marked success
- **WHEN** a held task succeeds with `mark-as-success`
#### Scenario: Held terminal task is indexed
- **WHEN** a task with mark-as-success policy succeeds while a result-retention hold is active
- **THEN** it appears in the success index immediately
- **AND** its result remains protected from disposal by the hold
Loading
Loading