feat(workflow): split X-Workflow-ID into definition + execution headers (closes #185)#197
Merged
Merged
Conversation
…rs (closes #185) The previously-overloaded X-Workflow-ID header conflated two distinct queries operators actually want: "show me this specific run" (per-run timeline) and "show me every run of this workflow" / "top failing workflows" (per-definition rollup). The code comment said "orchestrator workflow run" but the name read like a definition identifier, and there was no way to surface both axes on a single audit event. FORGE-2 splits the header into two distinct identifiers, mirroring industry precedent (GitHub Actions workflow + workflow_run_id, Tekton Pipeline + PipelineRun, Argo Workflow + WorkflowRun): - X-Workflow-ID is now the workflow DEFINITION id, stable across all runs of the same workflow. - X-Workflow-Execution-ID is the new per-run instance id, unique per workflow execution. Both surface as top-level fields on every audit event under a workflow run (workflow_id, workflow_execution_id) so SIEM consumers can build "per-run timeline" views (join on workflow_execution_id) AND "definition-level rollup" views (group by workflow_id) without joining on opaque ids. Clean break per the issue — no backward-compatibility alias since the contract is pre-production. Schema stays at version 1.0 because both new fields are omitempty and direct A2A invocations without orchestrator headers continue to emit byte-identical JSON. Pinned by TestWorkflowContextFromHTTPHeaders_{DefinitionAndExecutionAreIndependent, ExtractsAllFour}, TestApplyToHTTPHeaders_PopulatesExecutionID, TestRoundTripHTTPHeaders, TestEmitFromContext_{TagsWorkflowFieldsWhenContextHasThem, TagsBothWorkflowDefinitionAndExecution, OmitsWorkflowFieldsWhenContextEmpty}. Also stamps a new forge.workflow.execution.id OTel span attribute on the A2A dispatcher span (alongside the existing forge.workflow.id) so trace browsers can pivot on either axis.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Splits the previously-overloaded
X-Workflow-IDheader into two distinct identifiers so audit consumers can answer per-run AND per-definition queries from one event stream without joining on opaque ids:X-Workflow-ID— workflow definition (stable across all runs of the same workflow)X-Workflow-Execution-ID— per-run instance (unique per workflow execution) — newMirrored on every audit event as top-level
workflow_id(definition semantics) + newworkflow_execution_id(per-run semantics).Why
The code comment said "orchestrator workflow run" but the field name read like a definition identifier — a SIEM looking at the stream couldn't tell whether
workflow_idwas the join column for "this specific run" or "this workflow over time." This PR disambiguates the two axes following industry precedent:workflow+workflow_run_idPipeline+PipelineRunWorkflow+WorkflowRunBoth forms surface so operators get both queries cheaply:
workflow_execution_idworkflow_idChanges
Header convention
X-Workflow-IDworkflow_idX-Workflow-Execution-ID(new)workflow_execution_id(new)X-Workflow-Stage-IDstage_idX-Workflow-Step-IDstep_idX-Invocation-Callerinvocation_callerCode
forge-core/runtime/workflow.go—HeaderWorkflowExecutionIDconst;WorkflowContext.WorkflowExecutionIDfield;WorkflowContextFromHTTPHeaders/ApplyToHTTPHeadersread+write the new header;IsZerocovers it.forge-core/runtime/audit.go—AuditEvent.WorkflowExecutionID(json:"workflow_execution_id,omitempty");EmitFromContextstamps both fields from ctx.forge-core/observability/attrs.go— newAttrForgeWorkflowExecutionID = "forge.workflow.execution.id"span attribute.forge-cli/server/a2a_server.go— dispatcher span carries both attrs when the inbound carries the headers.forge-cli/runtime/runner.go— auth-audit emit sites passWorkflowExecutionIDfromwcthrough toauth_verify/auth_failevents.Docs
docs/security/workflow-correlation.md— headers table + "Why definition + execution" callout with industry precedent; updated audit-event example; updated wiring map.docs/security/audit-logging.md— schema-contract table, workflow-correlation prose, and FWS-3 attribution prose..claude/skills/forge.md— FWS-2 reference + audit-event field list + initiative table.Safety
omitempty— direct A2A invocations without orchestrator headers continue to emit byte-identical JSON to pre-FWS-2 consumers.schema_versionstays1.0(additive schema-compatible change).TestWorkflowContextFromHTTPHeaders_DefinitionAndExecutionAreIndependent.Test plan
golangci-lint runacross all four modules — 0 issuesgofmt -wacross all modulesgo test ./...inforge-core/andforge-cli/— all greenApplyToHTTPHeaderspopulates the new header,EmitFromContextstamps both fields, empty-ctx still omits both keys.tasks/sendwith both headers; confirm every emitted audit event carries bothworkflow_idandworkflow_execution_idat the top level.Cross-repo coordination
workflow_execution_id.This Forge PR is the foundational naming change the other repos plug into.