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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/disable-web-shared-decrypt-controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/web-shared': patch
---

Allow consumers to disable decrypt controls and explain why decryption is unavailable.
2 changes: 2 additions & 0 deletions .changeset/docs-v5-whats-new-pages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 6 additions & 0 deletions .changeset/log-order-draws.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'workflow': minor
'@workflow/core': minor
---

Pin correlation-ID draw order to event-log order (Node.js VM engine), so two concurrent replays of the same run assign the same IDs even when one loaded a shorter event-log prefix. Set `WORKFLOW_LOG_ORDER_DRAWS=0` to opt back into arrival-order delivery resolution.
5 changes: 5 additions & 0 deletions .changeset/preserve-run-key-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/world-vercel': patch
---

Preserve upstream HTTP status codes when fetching Workflow run encryption keys.
4 changes: 4 additions & 0 deletions .changeset/repro-poke-decay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Internal: the event-log-race-repro harness decays its `step-storm` poke cadence instead of stopping at the budget, and the local lanes get a run timeout sized for them.
5 changes: 5 additions & 0 deletions .changeset/sealed-log-noop-shared-predicate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/web-shared': patch
---

Render sealed log positions (`noop` events) as the log rows they are: shown in event lists, excluded from span geometry and trace duration, since a seal's timestamp belongs to whichever reader wrote it rather than to the run.
9 changes: 9 additions & 0 deletions .changeset/sealed-log-spec-seven.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@workflow/world': minor
'@workflow/world-vercel': minor
'@workflow/world-local': minor
'@workflow/world-postgres': minor
'@workflow/core': minor
---

New runs are created with the sealed-log event identity (specVersion 7). A sealed-log run's event positions are assigned by the backend before each write commits, so concurrent writers never contend for a position. A position whose writer dies is closed by the backend with a `noop` event; replay steps over those without delivering them or advancing the deterministic clock. Set `WORKFLOW_SEALED_LOG=0` to put a deployment back on the previous scheme. Every runtime reads sealed logs either way, and a run's version is fixed at creation, so the setting only affects new runs.
2 changes: 2 additions & 0 deletions .changeset/technical-writing-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
10 changes: 5 additions & 5 deletions .claude/agents/docs-writer.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ You are an expert technical writer specializing in developer documentation for t
- Highlight only the most relevant code to the concept being taught
- In examples showing workflows calling steps, put workflow code before step code
- Use proper type annotations to encourage best practices (e.g., `getWritable<MyType>()`)
- Remove type annotations when not needed (e.g., when just calling `.close()`)
- Remove type annotations when not needed (e.g., when calling `.close()`)

6. **Example-Driven Teaching**: Support explanations with working code examples that:
- Start simple and build incrementally
- Start with the minimum required code and build incrementally
- Show real-world use cases
- Include terse, focused comments that add value
- Use meaningful variable names that self-document intent
Expand Down Expand Up @@ -74,7 +74,7 @@ You are an expert technical writer specializing in developer documentation for t
- Use pipe syntax with double quotes for edge labels: `A -->|"label"| B`
- Highlight terminal states or key components with purple: `style NodeId fill:#a78bfa,stroke:#8b5cf6,color:#000`
- Place all `style` declarations at the end of the diagram
- Keep diagrams simple and readable - split into multiple diagrams if needed
- Keep diagrams focused and readable - split them into multiple diagrams if needed
- Add a legend or callout explaining highlighted nodes when appropriate

**When Creating New Documentation:**
Expand All @@ -98,10 +98,10 @@ You are an expert technical writer specializing in developer documentation for t
- Reference real implementation code when showing how features work internally

**Quality Checklist Before Finalizing:**
- Can a developer understand and use this feature after reading just the first example?
- Can a developer understand and use this feature after reading the first example?
- Is every technical term defined or linked to its definition?
- Are code examples syntactically correct and following project conventions?
- Does the explanation flow logically from simple to complex?
- Does the explanation flow logically from basic to complex?
- Have you eliminated all emojis and em-dashes?
- Is the writing concise without sacrificing clarity?
- Does the tone match canonical documentation like the directives guide?
Expand Down
15 changes: 10 additions & 5 deletions .github/scripts/render-event-log-race-repro-results.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,15 @@ function renderConfigTiming(config) {
? `burst ${config.resumeBurstOffsetMs}+${config.resumeBurstJitterMs ?? 0}ms`
: '',
config.pokeIntervalMs ? `poke ${config.pokeIntervalMs}ms` : '',
// The cap belongs next to the cadence it bounds: it BINDS on the local
// lanes (every step-storm run there reaches it), so a config line that
// showed only the cadence would read as far more sustained out-of-band
// pressure than the run actually received.
config.pokeMax ? `poke max ${config.pokeMax}` : '',
// The budget belongs next to the cadence it bounds: it is SPENT on the
// local lanes (every step-storm run there reaches it), so a config line
// showing only the cadence would read as a run-long stream of out-of-band
// writes at that rate, which is not what the later rounds received.
config.pokeMax
? `poke budget ${config.pokeMax}${
config.pokeDecayFactor ? ` then /${config.pokeDecayFactor}` : ''
}`
: '',
config.runTimeoutMs ? `timeout ${config.runTimeoutMs}ms` : '',
].filter(Boolean);
}
Expand Down Expand Up @@ -349,6 +353,7 @@ function compactConfig(config = {}) {
attrWrites: config.attrWrites,
pokeIntervalMs: config.pokeIntervalMs,
pokeMax: config.pokeMax,
pokeDecayFactor: config.pokeDecayFactor,
hookResumeStaggerMs: config.hookResumeStaggerMs,
launchStaggerMs: config.launchStaggerMs,
resumeBurstOffsetMs: config.resumeBurstOffsetMs,
Expand Down
21 changes: 13 additions & 8 deletions .github/scripts/render-event-log-race-repro-results.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,18 +451,23 @@ test('a local render omits the comment marker and the stored history', () => {
assert.ok(output.includes('### Run History'));
});

test('the poke ceiling is rendered next to the cadence it bounds', () => {
// The cap binds on the local lanes — every step-storm run there reaches it —
// so a config line naming only `poke 750ms` would advertise a run-long
// stream of out-of-band writes that the run did not receive. A number that
// silently truncates coverage has to appear next to the numbers it truncates.
test('the poke budget and its decay are rendered next to the cadence', () => {
// The budget is spent on the local lanes — every step-storm run there reaches
// it — so a config line naming only `poke 750ms` would advertise a run-long
// stream of out-of-band writes at that rate, which the later rounds did not
// receive. A bound that changes coverage has to appear next to what it bounds.
const withCap = runRender(
writeTempResults({
...resultsFor({ completed: 14 }),
config: { attempts: 14, pokeIntervalMs: 750, pokeMax: 64 },
config: {
attempts: 14,
pokeIntervalMs: 750,
pokeMax: 64,
pokeDecayFactor: 8,
},
})
);
assert.ok(withCap.includes('poke 750ms / poke max 64'));
assert.ok(withCap.includes('poke 750ms / poke budget 64 then /8'));

// Absent from an older history row's config, it renders nothing rather than
// an invented ceiling.
Expand All @@ -473,5 +478,5 @@ test('the poke ceiling is rendered next to the cadence it bounds', () => {
})
);
assert.ok(withoutCap.includes('poke 750ms'));
assert.ok(!withoutCap.includes('poke max'));
assert.ok(!withoutCap.includes('poke budget'));
});
12 changes: 11 additions & 1 deletion .github/workflows/event-log-race-repro.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ on:
description: 'step-storm: cadence of out-of-band resumes to the never-read poke hook'
required: false
poke_max:
description: 'step-storm: ceiling on poke resumes per run, so a slow run cannot collect unbounded out-of-band writes. Raise it with budget_ms when soaking'
description: 'step-storm: pokes at full cadence per run before the pump decays, so a slow run cannot bury itself in out-of-band writes'
required: false
poke_decay_factor:
description: 'step-storm: multiplier applied to poke_interval_ms once poke_max is spent (1 disables the decay)'
required: false
run_timeout_ms:
description: 'Per-run timeout before classifying as stuck'
Expand Down Expand Up @@ -155,6 +158,7 @@ jobs:
EVENT_LOG_RACE_REPRO_BLOCKED_BRANCH_WATCHDOG_MS: ${{ inputs.blocked_branch_watchdog_ms }}
EVENT_LOG_RACE_REPRO_POKE_INTERVAL_MS: ${{ inputs.poke_interval_ms }}
EVENT_LOG_RACE_REPRO_POKE_MAX: ${{ inputs.poke_max }}
EVENT_LOG_RACE_REPRO_POKE_DECAY_FACTOR: ${{ inputs.poke_decay_factor }}
EVENT_LOG_RACE_REPRO_RUN_TIMEOUT_MS: ${{ inputs.run_timeout_ms }}
EVENT_LOG_RACE_REPRO_BUDGET_MS: ${{ inputs.budget_ms }}

Expand Down Expand Up @@ -255,8 +259,14 @@ jobs:
EVENT_LOG_RACE_REPRO_WATCHDOG_MS: ${{ inputs.watchdog_ms }}
EVENT_LOG_RACE_REPRO_STEP_DELAY_MS: ${{ inputs.step_delay_ms }}
EVENT_LOG_RACE_REPRO_HOOK_RESUME_STAGGER_MS: ${{ inputs.hook_resume_stagger_ms }}
EVENT_LOG_RACE_REPRO_BLOCKED_BRANCH_ATTEMPTS: ${{ inputs.blocked_branch_attempts }}
EVENT_LOG_RACE_REPRO_LAUNCH_STAGGER_MS: ${{ inputs.launch_stagger_ms }}
EVENT_LOG_RACE_REPRO_RESUME_BURST_OFFSET_MS: ${{ inputs.resume_burst_offset_ms }}
EVENT_LOG_RACE_REPRO_RESUME_BURST_JITTER_MS: ${{ inputs.resume_burst_jitter_ms }}
EVENT_LOG_RACE_REPRO_BLOCKED_BRANCH_WATCHDOG_MS: ${{ inputs.blocked_branch_watchdog_ms }}
EVENT_LOG_RACE_REPRO_POKE_INTERVAL_MS: ${{ inputs.poke_interval_ms }}
EVENT_LOG_RACE_REPRO_POKE_MAX: ${{ inputs.poke_max }}
EVENT_LOG_RACE_REPRO_POKE_DECAY_FACTOR: ${{ inputs.poke_decay_factor }}
EVENT_LOG_RACE_REPRO_RUN_TIMEOUT_MS: ${{ inputs.run_timeout_ms }}
EVENT_LOG_RACE_REPRO_BUDGET_MS: ${{ inputs.budget_ms }}

Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ jobs:
exit 1
fi

no-unrun-tests:
name: No Unrun Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4

- name: Setup Node.js 22.x
uses: actions/setup-node@v4
with:
node-version: 22.x

# `turbo test` only runs a package's tests if that package declares a
# `test` script, so a suite can exist for months without ever running.
# See #3731.
- name: Check every test file is run by turbo test or a CI lane
run: node scripts/check-no-unrun-tests.mjs

scripts-tests:
name: CI Scripts Tests
runs-on: ubuntu-latest
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,16 @@ jobs:
APP_NAME: ${{ matrix.app.name }}
WORKBENCH_APP_PATH: ${{ steps.prepare-workbench.outputs.workbench_app_path }}

# Asserts the shape of the manifest the step above just built. It needs
# its own invocation rather than another file in that one: local-build
# is what runs `pnpm build`, and vitest would run both files
# concurrently, against a manifest that does not exist yet.
- name: Run Manifest Tests
run: pnpm vitest run packages/core/e2e/manifest.test.ts
env:
APP_NAME: ${{ matrix.app.name }}
WORKBENCH_APP_PATH: ${{ steps.prepare-workbench.outputs.workbench_app_path }}

- name: Run E2E Tests
run: |
export PROD_SERVER_LOG_PATH="$GITHUB_WORKSPACE/prod-server-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.log"
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ event-log-race-repro-results.json
event-log-race-repro-summary.md
event-log-race-repro-previous-comment.md
event-log-race-repro-server.log

# Per-run e2e diagnostics sidecars written to the repo root by the harness
# (writeDiagnosticsSidecar in packages/core/e2e/utils.ts)
e2e-diagnostics-*.json
Loading
Loading