Skip to content

Commit d5ddb13

Browse files
committed
docs: state the wire-marker deploy ordering and the options-type rename
- The v8 upgrade guide now has a rolling-upgrade warning for the typed-error wire marker. A data-less contract error emitted by a 7.x worker carries no marker, so an 8.0 reader degrades it to a generic failure — silently, unless `onRehydrationMiss` is registered. Documents workers-before-callers ordering, the hook, and that schema-bearing errors are unaffected. Added to the checklist. - `ActivityDefaultOptions` -> `ContractActivityOptions` in the rename table, the checklist, the contract-surface reference, and the changeset.
1 parent 61978fc commit d5ddb13

3 files changed

Lines changed: 44 additions & 6 deletions

File tree

.changeset/v8-audit-remediation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ v8 audit remediation — a second full-surface pass hardening robustness, the un
1616
**`@temporal-contract/contract`:**
1717

1818
- Type-helper renames to the family-standard `Infer*` prefix: `SignalNamesOf`/`QueryNamesOf`/`UpdateNamesOf`/`DeclaredErrorsOf``InferSignalNames`/`InferQueryNames`/`InferUpdateNames`/`InferDeclaredErrors`.
19-
- `defineActivity`'s `defaultOptions` key is renamed `activityOptions` (merge precedence unchanged).
19+
- `defineActivity`'s `defaultOptions` key is renamed `activityOptions` (merge precedence unchanged), and its type `ActivityDefaultOptions``ContractActivityOptions` — the new name keeps the contract-level, portable subset distinct from Temporal's own `ActivityOptions`, which is what the worker-side `activityOptionsByName` overrides take.
2020
- Duration strings are validated against the `ms` grammar at `defineContract` time — `"5 minutos"`, `""`, and negative durations now fail at definition, naming the offending path, instead of at the worker.
2121
- Temporal-reserved names are rejected at `defineContract`: the `__temporal_` prefix and the exact `__stack_trace` / `__enhanced_stack_trace` query names.
2222
- Activity-only contracts are allowed — `workflows` may be `{}` when at least one global activity is declared (dedicated activity-pool task queues).

docs/how-to/upgrade-to-v8.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,7 @@ integration, and family consistency. These land in the same major.
642642
| `SignalNamesOf` / `QueryNamesOf` / `UpdateNamesOf` | `InferSignalNames` / `InferQueryNames` / `InferUpdateNames` |
643643
| `DeclaredErrorsOf` | `InferDeclaredErrors` |
644644
| `defineActivity({ defaultOptions })` | `defineActivity({ activityOptions })` |
645+
| `ActivityDefaultOptions` (type) | `ContractActivityOptions` |
645646
| `context.defineSignal` / `defineQuery` / `defineUpdate` | `context.handleSignal` / `handleQuery` / `handleUpdate` |
646647
| `@temporal-contract/contract/result-async` | removed — internals live at `.../internal` (private) |
647648

@@ -697,6 +698,37 @@ it. Re-raise with the new `rethrowCancellation(error)` from
697698
`@temporal-contract/worker/workflow`. See
698699
[Handle cancellation](/how-to/handle-cancellation).
699700

701+
### Typed errors carry a wire marker — mind the deploy order
702+
703+
A contract error now crosses the wire with a provenance marker in
704+
`ApplicationFailure.details[1]` (`{ $tc: 1 }`). For an error that declares a
705+
`data` schema, validating `details[0]` is still the gate. For a **data-less**
706+
error the marker is **required** — that closes a false positive where any
707+
unrelated `ApplicationFailure` whose `type` happened to equal a declared
708+
data-less error name was surfaced as the typed domain error.
709+
710+
::: warning Rolling upgrades
711+
The marker is written by 8.0 workers only. During a rolling deploy, a
712+
**data-less** contract error emitted by a still-7.x worker carries no marker,
713+
so an 8.0 workflow or client will not rehydrate it — it degrades to the
714+
generic failure classification. Nothing throws and nothing is logged by
715+
default, so a `match` arm keyed on the typed error silently stops matching for
716+
the duration of the window.
717+
718+
Order the deploy **workers first, then clients/callers**, and drain in-flight
719+
executions before cutting callers over. To make the window observable, register
720+
the diagnostic hook — it fires on every degrade-to-generic:
721+
722+
```typescript
723+
import { onRehydrationMiss } from "@temporal-contract/contract/errors";
724+
725+
onRehydrationMiss((miss) => logger.warn({ miss }, "contract error degraded to generic"));
726+
```
727+
728+
Errors that declare a `data` schema are unaffected: they rehydrate on schema
729+
validation, marker or not.
730+
:::
731+
700732
### Worker: safer declarations
701733

702734
- A shared activity referenced from several scopes must be the **same function
@@ -772,7 +804,8 @@ contract-error wire round-trip) so a test fails exactly where production does.
772804
- [ ] No schema relies on its transform running on the send side (each
773805
boundary now parses once, on receive)
774806
- [ ] `SignalNamesOf` / `QueryNamesOf` / `UpdateNamesOf` / `DeclaredErrorsOf`
775-
→ the `Infer*` prefix; `defineActivity` `defaultOptions``activityOptions`
807+
→ the `Infer*` prefix; `defineActivity` `defaultOptions``activityOptions`;
808+
the `ActivityDefaultOptions` type → `ContractActivityOptions`
776809
- [ ] `context.defineSignal` / `defineQuery` / `defineUpdate`
777810
`handleSignal` / `handleQuery` / `handleUpdate`
778811
- [ ] Every `qualifyFailure(...)` passes `{ expected }` (or `{ expected: "any" }`
@@ -786,6 +819,9 @@ contract-error wire round-trip) so a test fails exactly where production does.
786819
- [ ] `createContractTest({ contract, ... })` and `runActivity(def, { ... })`
787820
use the option bag; `testcontainers` installed only where `createContractTest` runs
788821
- [ ] Imports of `@temporal-contract/contract/result-async` removed
822+
- [ ] Rolling deploy ordered **workers before callers**, and `onRehydrationMiss`
823+
registered — a data-less contract error from a 7.x worker carries no wire
824+
marker and degrades to a generic failure until the workers are cut over
789825
- [ ] `@temporalio/*` resolve to `^1.16.0`; no CJS `require` of these packages
790826
- [ ] `pnpm typecheck` clean
791827

docs/reference/contract-surface.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,13 @@ relaxes when the contract exists purely to serve activities.
7575
| `input` | `AnySchema` | yes |
7676
| `output` | `AnySchema` | yes |
7777
| `errors` | `Record<string, ErrorDefinition>` | no |
78-
| `activityOptions` | `ActivityDefaultOptions` | no |
78+
| `activityOptions` | `ContractActivityOptions` | no |
7979

8080
::: info Renamed in 8.0
8181
The contract-level activity-options field is `activityOptions` (it was
82-
`defaultOptions` before 8.0). Its type is still `ActivityDefaultOptions`.
82+
`defaultOptions` before 8.0), and its type is `ContractActivityOptions` (it
83+
was `ActivityDefaultOptions`). The type name is now distinct from Temporal's
84+
own `ActivityOptions`, which is what the worker-side overrides take.
8385
:::
8486

8587
### `defineSignal(definition?)`
@@ -159,12 +161,12 @@ type ErrorDefinition = {
159161
| `message` | Default message. Overridable per instance |
160162
| `nonRetryable` | `true` stops Temporal retrying. Default `false` |
161163

162-
### `ActivityDefaultOptions`
164+
### `ContractActivityOptions`
163165

164166
A strict object — unknown keys are rejected at `defineContract` time.
165167

166168
```typescript
167-
type ActivityDefaultOptions = {
169+
type ContractActivityOptions = {
168170
startToCloseTimeout?: DurationValue;
169171
scheduleToCloseTimeout?: DurationValue;
170172
scheduleToStartTimeout?: DurationValue;

0 commit comments

Comments
 (0)