Skip to content

refactor(workflow-executor): rebuild field displayNames dynamically#1618

Open
PMerlet wants to merge 9 commits into
feat/prd-214-server-step-mapperfrom
feature/prd-426-always-rebuild-field-displaynames-dynamically
Open

refactor(workflow-executor): rebuild field displayNames dynamically#1618
PMerlet wants to merge 9 commits into
feat/prd-214-server-step-mapperfrom
feature/prd-426-always-rebuild-field-displaynames-dynamically

Conversation

@PMerlet
Copy link
Copy Markdown
Member

@PMerlet PMerlet commented Jun 2, 2026

fixes PRD-426

What & why

The executor persisted field/action/relation displayName in step execution data. Labels come from the per-rendering layout and change, so replayed/resumed runs showed stale labels. Now we persist only the technical name (fieldName / action name) and rebuild displayName from the current schema on read (GET /runs/:runId + AI step summaries).

No DB migration — execution data is a single JSON column; old rows with a leftover displayName read fine (the technical name is the source of truth).

Core change

  • step-execution-data.ts: refs are technical-name-only; added a Displayed* / HydratedStepExecutionData view for reads.
  • Executors stop writing displayName; new hydrate-step-execution-data.ts re-injects it from the schema.
  • runner.getRunStepExecutions and buildPreviousStepsMessages hydrate on read; load-related PATCH validator drops displayName.

QA hardening

  1. Global fetch de-dup in SchemaCache.getOrFetch — concurrent callers share one fetch (no stampede).
  2. Per-row resilience — a malformed row is returned un-hydrated instead of 500-ing the whole read.
  3. Logging on schema-fetch / hydration failures (no silent degradation).
  4. Rendering-scoped cache — keyed by (renderingId, collectionName); displayName and visible fields are rendering-specific, so the old collectionName-only key leaked labels across renderings in one environment.
  5. renderingId validation on the read route (400 if missing/invalid) — Macroscope fix.
  6. TTL trade-off documented: reads serve from a 10-min cache by design.

Tests

877 passing / 40 suites, lint clean; hydrate-step-execution-data.ts + schema-cache.ts at 100%. Covers changed-schema rebuild, malformed rows, fetch de-dup, and cross-rendering isolation.

Known limitations (intentional)

  • Read-path cache uses the requesting user's renderingId — correct for viewing your own run; a cross-rendering view is bounded.
  • getRelatedData PK-fallback-to-['id'] on cache miss — pre-existing, WIP related-data code, left as-is.

Targets feat/prd-214-server-step-mapper (package not on main yet).

Note

Rebuild field displayNames dynamically from schema at read time instead of persisting them

  • Removes displayName from all persisted step execution data (FieldRef, ActionRef, RelationRef) across executors for read-record, update-record, trigger-action, and load-related-record steps.
  • Adds a hydrateStepExecutionData function in hydrate-step-execution-data.ts that re-derives display names from the current schema at read time, with graceful fallback to technical names on fetch or hydration failures.
  • Introduces HydratedStepExecutionData and Displayed* type variants in step-execution-data.ts to represent the enriched view returned to callers.
  • Scopes SchemaCache by renderingId with concurrent fetch deduplication via getOrFetch, and updates Runner.getRunStepExecutions to require renderingId as a second argument.
  • Behavioral Change: GET /runs/:runId now returns 400 if the bearer token lacks a valid renderingId; persisted step execution records no longer contain displayName fields.

Macroscope summarized be74367.

Persist only technical names (fieldName / action name) in step execution
data and re-derive displayName from the collection schema at read time, so
labels edited in the rendering never go stale on replay or resume.

- step-execution-data: strip displayName from persisted refs; add a
  Displayed*/Hydrated view union for read-time consumption
- executors (update/trigger/load-related/read): stop persisting displayName
- add hydrateStepExecutionData() + makeSchemaGetter(), used by
  runner.getRunStepExecutions and the AI step summaries
- drop displayName from the load-related-record PATCH validator

No DB migration: execution data is a single JSON column and old rows with a
leftover displayName still read fine (the technical name is the source of
truth).

fixes PRD-426

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@linear-code
Copy link
Copy Markdown

linear-code Bot commented Jun 2, 2026

PRD-426

@qltysh
Copy link
Copy Markdown

qltysh Bot commented Jun 2, 2026

5 new issues

Tool Category Rule Count
qlty Structure Function with many returns (count = 4): format 3
qlty Structure Function with high complexity (count = 17): build 1
qlty Structure Function with many parameters (count = 4): makeSchemaGetter 1

… into feature/prd-426-always-rebuild-field-displaynames-dynamically

# Conflicts:
#	packages/workflow-executor/src/executors/load-related-record-step-executor.ts
#	packages/workflow-executor/src/http/pending-data-validators.ts
@qltysh
Copy link
Copy Markdown

qltysh Bot commented Jun 2, 2026

Qlty


Coverage Impact

Unable to calculate total coverage change because base branch coverage was not found.

Modified Files with Diff Coverage (11)

RatingFile% DiffUncovered Line #s
New Coverage rating: A
packages/workflow-executor/src/runner.ts100.0%
New Coverage rating: A
...ow-executor/src/executors/load-related-record-step-executor.ts100.0%
New Coverage rating: A
...s/workflow-executor/src/executors/read-record-step-executor.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/executors/record-step-executor.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/executors/base-step-executor.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/http/executor-http-server.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/schema-cache.ts100.0%
New Coverage rating: A
...workflow-executor/src/executors/update-record-step-executor.ts100.0%
New Coverage rating: A
...-executor/src/executors/trigger-record-action-step-executor.ts100.0%
New Coverage rating: A
...ages/workflow-executor/src/adapters/agent-client-agent-port.ts100.0%
New Coverage rating: A
packages/workflow-executor/src/hydrate-step-execution-data.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

PMerlet and others added 4 commits June 2, 2026 18:32
…ield and fallthrough paths

Adds cases for the awaiting-input / executing / automatic phases (where
executionParams or pendingData is absent), the relation-result undefined
branch, the action-vs-field lookup, and the unrecognized-type fallthrough.
Brings hydrate-step-execution-data.ts to 100% statement/branch/function/line
coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eads

QA hardening of the read-time hydration path:

- De-duplicate concurrent schema fetches: makeSchemaGetter now shares an
  in-flight promise per collection, so hydrating N steps of one collection
  (Promise.all in getRunStepExecutions / buildPreviousStepsMessages) triggers
  a single orchestrator call instead of N.
- Tolerate malformed/legacy rows: a throw while hydrating one execution is
  caught and the raw execution returned, so one bad row no longer 500s the
  whole GET /runs/:runId response.
- Observability: schema-fetch failures (warn) and per-row hydration failures
  (error) are now logged with collection/type/stepIndex context instead of
  failing silently.

Tests cover the stampede de-dup, failed-fetch retry/no-cache, malformed-row
resilience, and both log paths. hydrate-step-execution-data.ts stays at 100%
coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oss-rendering label leaks

displayName, and the visible field/action set, are rendering-specific (forestadmin-server
resolves them from the run's rendering layout). The process-wide SchemaCache was keyed by
collectionName only, so within one environment a run on rendering A could serve its labels /
visible fields to a run on rendering B for the cache TTL — wrong labels and, when a rendering
hides fields, a wrong field set fed to the AI.

- SchemaCache is now keyed by (renderingId, collectionName) via nested maps; the bare iterator
  is replaced by entriesForRendering(renderingId).
- makeSchemaGetter takes renderingId (cache scope) alongside runId (server fetch, which resolves
  the run's rendering). RecordStepExecutor.getCollectionSchema delegates to it for consistent
  keying + dedup; buildPreviousStepsMessages and getRunStepExecutions pass the rendering.
- getRunStepExecutions(runId, renderingId): the HTTP read path passes the requesting user's
  renderingId. AgentClientAgentPort scopes resolveSchema/buildActionEndpoints by user.renderingId.

Technical identifiers (fieldName, action name, primary keys, endpoints) are environment-level and
were never corrupted; this fix isolates the rendering-scoped labels and field membership.

Tests: SchemaCache rendering isolation + entriesForRendering, makeSchemaGetter per-rendering
isolation, and an end-to-end runner test proving two renderings sharing one cache don't
contaminate. schema-cache.ts and hydrate-step-execution-data.ts at 100% coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…f on reads

Record that label edits can take up to the 10-minute TTL to surface on the hydration read path
as an accepted trade-off, so it reads as intentional rather than a defect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/workflow-executor/src/http/executor-http-server.ts
PMerlet and others added 3 commits June 2, 2026 19:21
…read route

Addresses Macroscope review: handleGetRun cast the JWT payload to StepUser and passed
renderingId downstream without runtime validation. A token missing renderingId (or with a
non-numeric value) would send undefined/NaN into the schema cache. Mirror the bearerUserId guard
used in handleTrigger: coerce to number, and return 400 when it is not finite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r-getter

The in-flight de-dup previously lived on each getter (one per getRunStepExecutions call / per
executor step), so it only collapsed concurrent misses within a single read. Two independent
concurrent readers of the same cold (rendering, collection) — two reads of one run, or two runs
of one rendering — still stampeded the orchestrator until the first fetch resolved.

Move the read-through + de-dup into SchemaCache.getOrFetch, keyed by (renderingId, collectionName)
on the one shared cache. All callers now share a single in-flight promise per (rendering,
collection); makeSchemaGetter is a thin wrapper. A rejected fetch is not cached and clears the
in-flight slot so the next caller retries.

Tests: getOrFetch (hit/miss/concurrent-share/per-rendering/failed-retry) and a cross-getter
global de-dup test. schema-cache.ts and hydrate-step-execution-data.ts at 100% coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ration code

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant