Skip to content

Commit e7a9ef4

Browse files
btraversclaude
andcommitted
chore!: release prep — package checks, changesets, and reconciliation for the v8 review remediation
- Adopt publint --strict + attw --pack as a per-package check:package turbo task (contract/testing: esm-only profile; worker: node16 — subpath-only exports can't resolve under node10; client: full). - Fix a real release blocker check:package caught: the testing package's workspace:^ sibling peers broke pnpm pack/publish (ERR_PNPM_CANNOT_RESOLVE_WORKSPACE_PROTOCOL, the peers are deliberately not installed to avoid a turbo package-graph cycle) — they now use concrete ^8.0.0-beta.3 ranges that changesets keeps bumped. - One major changeset for all four packages summarizing the v8 review remediation breaks. - Docs reconciliation: testing API guide sections (createContractTest, runActivity, createTimeSkippingTest, createGlobalSetup), example package descriptions, testing peer requirements in install docs and README, stale zod-internal claims, dependencies.md testing peers + no-devDep cycle exception, getHandle TSDoc example narrows with isOk() (unthrown 5). - Register second.workflows.ts as a knip entry (loaded by path string). - Mark both v8 specs implemented; D4 scope cuts recorded as still deferred. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 0cad3f1 commit e7a9ef4

18 files changed

Lines changed: 629 additions & 54 deletions

.agents/rules/dependencies.md

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
## Key Dependencies
44

5-
| Dependency | Where it's used |
6-
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
7-
| `@temporalio/client` | Temporal client SDK — peer dep of `client` |
8-
| `@temporalio/worker` | Temporal worker SDK — peer dep of `worker` |
9-
| `@temporalio/workflow` | Temporal workflow API — peer dep of `worker` |
10-
| `@temporalio/common` | Shared Temporal types — peer dep of `client`/`worker` |
11-
| `@temporalio/testing` | Time-skipping test server (`TestWorkflowEnvironment`) — peer dep of `testing` |
12-
| `@standard-schema/spec` | Standard Schema specification — direct dep |
13-
| `unthrown` | `Result` / `AsyncResult` — peer dep of `client`/`worker` |
14-
| `zod` | Direct dep of `contract` (used internally for the `defineContract` runtime validation pass); user-side schema lib for the others |
15-
| `valibot` / `arktype` | User-side schema libraries (Standard Schema) |
5+
| Dependency | Where it's used |
6+
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------- |
7+
| `@temporalio/client` | Temporal client SDK — peer dep of `client` |
8+
| `@temporalio/worker` | Temporal worker SDK — peer dep of `worker` |
9+
| `@temporalio/workflow` | Temporal workflow API — peer dep of `worker` |
10+
| `@temporalio/common` | Shared Temporal types — peer dep of `client`/`worker` |
11+
| `@temporalio/testing` | Time-skipping test server (`TestWorkflowEnvironment`) — peer dep of `testing` |
12+
| `@standard-schema/spec` | Standard Schema specification — direct dep |
13+
| `unthrown` | `Result` / `AsyncResult` — peer dep of `client`/`worker` |
14+
| `zod` | User-side schema library (Standard Schema); dev-only in this repo — `defineContract`'s structural validation is hand-rolled |
15+
| `valibot` / `arktype` | User-side schema libraries (Standard Schema) |
1616

1717
`pino` appears in the catalog and is used by `examples/` only — it's not imported from any published package's `src/`.
1818

@@ -38,15 +38,17 @@ All dependency versions are centralized in `pnpm-workspace.yaml` under the `cata
3838

3939
Anything that appears in a published package's **public type signatures** must be a peer dep, not a regular dep — otherwise downstream consumers can end up with two disjoint nominal types in their typechecker (theirs and ours), even though the runtime classes are compatible.
4040

41-
| Package | Peer dependencies |
42-
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43-
| client | `@temporalio/client ^1`, `@temporalio/common ^1`, `unthrown ^5` |
44-
| worker | `@temporalio/common ^1`, `@temporalio/worker ^1`, `@temporalio/workflow ^1`, `unthrown ^5` |
45-
| contract | `unthrown ^5` (optional — only needed when using `result-async`) |
46-
| testing | `vitest ^4` (the `globalSetup` hook integrates with vitest's test runner), `@temporalio/client ^1`, `@temporalio/testing ^1`, `@temporalio/worker ^1` (all exposed by the fixtures' public types — e.g. `TestWorkflowEnvironment` in `time-skipping`) |
41+
| Package | Peer dependencies |
42+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
43+
| client | `@temporalio/client ^1`, `@temporalio/common ^1`, `unthrown ^5` |
44+
| worker | `@temporalio/common ^1`, `@temporalio/worker ^1`, `@temporalio/workflow ^1`, `unthrown ^5` |
45+
| contract | `unthrown ^5` (optional — only needed when using `result-async`) |
46+
| testing | `@temporal-contract/client`, `@temporal-contract/contract`, `@temporal-contract/worker` (concrete `^8.x` ranges — see below), `unthrown ^5` (the contract-aware fixtures expose `TypedClient`/`ContractClient`/`ActivitiesHandler`/`AsyncResult` in their public types), `vitest ^4` (the `globalSetup` hook integrates with vitest's test runner), `@temporalio/client ^1`, `@temporalio/testing ^1`, `@temporalio/worker ^1` (all exposed by the fixtures' public types — e.g. `TestWorkflowEnvironment` in `time-skipping`) |
4747

4848
When you add a peer dep, also add it to `devDependencies` (with the same `"catalog:"` reference) so the local workspace build still resolves it. The workspace has `autoInstallPeers: false`, so peers must be present somewhere on the install side.
4949

50+
**Deliberate exception:** the testing package's `@temporal-contract/*` sibling peers have NO matching devDeps. client and worker devDepend on testing for their integration fixtures, so adding the siblings to testing's devDeps would put a cycle in the package graph, which turbo 2 rejects. Local resolution goes through tsconfig `paths` mapped to the siblings' sources plus vitest aliases instead — see the comments in `packages/testing/tsconfig.json` and `packages/testing/vitest.config.ts`. For the same reason these peers use **concrete `^8.x` semver ranges, not `workspace:^`**: pnpm can only rewrite the `workspace:` protocol at pack/publish time for deps that are actually installed, so `workspace:^` here breaks `pnpm pack`/`pnpm publish` (`ERR_PNPM_CANNOT_RESOLVE_WORKSPACE_PROTOCOL`). Changesets keeps the ranges bumped (`updateInternalDependencies: "patch"`, and the four packages are a fixed group).
51+
5052
## Security `overrides` (`pnpm-workspace.yaml`)
5153

5254
`pnpm-workspace.yaml` pins minimum versions for transitive dependencies via its `overrides:` block to close known CVEs (currently `fast-uri`, `protobufjs`, and `testcontainers>undici`). When a security audit flags a new vulnerability, add the pin there (with a comment citing the GHSA and the reachability reasoning) rather than waiting for upstream to update. Advisories that are unreachable in this repo are suppressed via `auditConfig.ignoreGhsas`, each with a documented justification.

.agents/specs/2026-07-29-typed-client-contract-binding-design.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Design: decouple the client from the contract
22

33
- **Date:** 2026-07-29
4-
- **Status:** approved, not yet implemented
4+
- **Status:** implemented (`feat/v8-full-review-fixes`)
55
- **Target:** `@temporal-contract/client`, 8.0 beta line
66
- **Origin:** [millenium!56319 note 2210871](https://gitlab.factory.fonciamillenium.net/FonciaStark/millenium/-/merge_requests/56319#note_2210871)
77

.agents/specs/2026-07-30-v8-review-remediation.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# v8 review remediation — decisions and work breakdown
22

33
- **Date:** 2026-07-30
4-
- **Status:** in progress on `feat/v8-full-review-fixes`
4+
- **Status:** implemented on `feat/v8-full-review-fixes` (Waves 1-4 complete)
55
- **Origin:** full six-track review (client, worker, contract+testing, unthrown audit,
66
amqp-contract consistency, DX/docs) performed 2026-07-29/30. This spec records the
77
decisions and the fix plan. All breaking changes land inside the unshipped 8.0 beta.
@@ -53,11 +53,14 @@ infinite Workflow Task retries. Decision: introduce a `ContractMisuseError` exte
5353

5454
### D4. Scope cuts (deferred, not forgotten)
5555

56-
- Typed **local activities** path — new feature, separate spec.
57-
- Client `list`/`count` — covered by the new raw escape hatch for now.
58-
- amqp-contract / async-contract convergence (`declare*` verbs, `for()` in amqp,
59-
`sideEffects`) — separate repos, separate track.
60-
- Configurable invalid-signal policy (D2) and per-binding interceptor overrides.
56+
Checked = still deferred as of Wave 4 completion (nothing below landed in this work):
57+
58+
- [x] Typed **local activities** path — new feature, separate spec. _Still deferred._
59+
- [x] Client `list`/`count` — covered by the new raw escape hatch for now. _Still deferred._
60+
- [x] amqp-contract / async-contract convergence (`declare*` verbs, `for()` in amqp,
61+
`sideEffects`) — separate repos, separate track. _Still deferred._
62+
- [x] Configurable invalid-signal policy (D2) and per-binding interceptor overrides.
63+
_Still deferred._
6164

6265
## Work breakdown
6366

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
"@temporal-contract/contract": major
3+
"@temporal-contract/client": major
4+
"@temporal-contract/worker": major
5+
"@temporal-contract/testing": major
6+
---
7+
8+
v8 review remediation — the full-surface overhaul from the six-track 8.0 review. Headline breaks, per package:
9+
10+
**All boundaries (client + worker):** payloads are now parsed exactly once, on the receiving side. The sender still validates (surfacing a typed `Err`/`ValidationError` early) but transmits the caller's original value, so transforming schemas (`z.coerce.*`, `.transform(...)`) apply once end-to-end instead of twice.
11+
12+
**`@temporal-contract/contract`:**
13+
14+
- `defineContract`'s structural validation is hand-rolled and strict (unknown keys rejected); the zod runtime dependency is gone.
15+
- `input` is optional on signal/query/update definitions — `defineSignal()` / `defineQuery({ output })` with no input means the handler receives `undefined`, no `z.void()` ceremony.
16+
- ESM-only build (the CJS artifacts and `main`/`module`/`require` conditions are removed).
17+
- `InferContractWorkflows` is deleted (trivial alias).
18+
19+
**`@temporal-contract/client`:**
20+
21+
- `TypedClient` is split from the contract: `TypedClient.create({ client })` is connection-scoped, and `typedClient.for(contract)` returns the contract-bound `ContractClient` with the workflow/schedule methods. A `readonly raw` escape hatch exposes the underlying `Client`.
22+
- `getHandle` is synchronous, returns `Result<TypedWorkflowHandle, WorkflowNotInContractError>`, and accepts `runId`/`firstExecutionRunId` options; handles carry `runId`/`firstExecutionRunId`; typed `startUpdate` joins `executeUpdate`.
23+
- `WorkflowNotFoundError` is renamed `WorkflowNotInContractError`.
24+
- Schedule surface parity: typed `ScheduleAlreadyExistsError`/`ScheduleNotFoundError` on the error channel (instead of defects), plus `update`, `backfill`, and `list`.
25+
- The six unused `ClientInfer*` type exports are deleted.
26+
27+
**`@temporal-contract/worker`:**
28+
29+
- Invalid signal payloads are dropped and logged (`log.warn`), never thrown — `SignalInputValidationError` is deleted; a stale client can no longer terminally kill a workflow execution.
30+
- Contract misuse inside workflow code (unknown signal/query/update or workflow name, async schema, uncovered activity options) now fails fast as a non-retryable `ContractMisuseError` `ApplicationFailure` instead of hanging executions in Workflow Task retries.
31+
- Exported `qualify` is renamed `qualifyFailure` (no alias).
32+
- `activities` is optional on `createWorker`, and activity-less workflows no longer need `{}` entries in the implementations map.
33+
- `TypedChildWorkflowHandle` gains a typed `signals` map and `firstExecutionRunId`.
34+
35+
**`@temporal-contract/testing`:**
36+
37+
- New contract-aware fixtures: `createContractTest(contract, { workflowsPath, activities?, workerOptions? })` yields `{ client, typedClient, worker }` against the testcontainers server, and `runActivity(definition, implementation, input, { env? })` runs one implementation inside `MockActivityEnvironment` (vitest-free).
38+
- Configurable environments: `createTimeSkippingTest(options?)` / `createTimeSkippingEnvironment(options?)` forward `TimeSkippingTestWorkflowEnvironmentOptions`; `createGlobalSetup({ postgresImage?, temporalImage?, temporalEnv?, quiet? })` pins container images and env.
39+
- The package now peer-depends on `@temporal-contract/contract`, `@temporal-contract/client`, `@temporal-contract/worker`, and `unthrown`.

docs/examples/index.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ of the same thing step by step.
1111

1212
Three packages, mirroring how a real deployment splits:
1313

14-
| Package | Contents |
15-
| --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
16-
| [`order-processing-contract`](https://github.com/btravstack/temporal-contract/tree/main/examples/order-processing-contract) | Schemas and the contract. Depended on by the other two |
17-
| [`order-processing-worker`](https://github.com/btravstack/temporal-contract/tree/main/examples/order-processing-worker) | Activities, workflow, worker, integration tests |
18-
| [`order-processing-client`](https://github.com/btravstack/temporal-contract/tree/main/examples/order-processing-client) | Starts workflows and handles results |
14+
| Package | Contents |
15+
| --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16+
| [`order-processing-contract`](https://github.com/btravstack/temporal-contract/tree/main/examples/order-processing-contract) | The shared contract, built composition-first with the `define*` helpers: signals (payload-carrying and payload-less), an argument-less query, a `PaymentDeclined` typed error shared by activity and workflow, and a schedule-ready, activity-less cleanup workflow. Depended on by the other two |
17+
| [`order-processing-worker`](https://github.com/btravstack/temporal-contract/tree/main/examples/order-processing-worker) | Clean-architecture worker: `AsyncResult` activities with `qualifyFailure` and typed error constructors, a `condition`-based approval gate with signal/query handlers, an activity-less schedule-driven workflow, and integration tests |
18+
| [`order-processing-client`](https://github.com/btravstack/temporal-contract/tree/main/examples/order-processing-client) | `TypedClient.create({ client }).for(contract)` in action: typed signals/queries through handles, the synchronous `getHandle`, exhaustive `match` + `P.tag` including the rehydrated `PaymentDeclined` contract error, and `schedule.create` with the create-if-absent idiom |
1919

2020
### What it demonstrates
2121

0 commit comments

Comments
 (0)