Skip to content

A3: structured StepOutcome surfaced through CommandExecutionResult

Placeholder ppxd requested to merge feat/calamari-structured-step-outcomes into main

Summary

Adds a structured per-step outcome record alongside the existing free-form Console.WriteLine logs. Future UI dashboards / log analytics / SDK consumers can now read per-step metrics without parsing logs.

Existing log lines preserved — operators still grep them for human-readable summaries. The structured outcome is additive.

What's in the box

Layer File
Record + enum + interface src/Squid.Calamari/Pipeline/StepOutcome.cs (new)
Context implements interface src/Squid.Calamari/Commands/RunScriptCommandPipeline.cs
Result exposes list src/Squid.Calamari/Execution/CommandExecutionResult.cs
Outcomes emitted by every step 7 step files (Extract, SubstituteInFiles, ConfigurationTransforms, StructuredConfig, ConventionScript, DeployFailedConvention, ExecuteScriptWithEngine, WriteBootstrappedScript)
Tests StepOutcomeTests.cs (6) + StepOutcomeEmissionTests.cs (8)

Per-step metrics emitted

Step Metrics keys
ExtractPackage FilesExtracted, TotalBytesWritten
SubstituteInFiles FilesProcessed, FilesSkipped, FilesWithUnresolvedTokens
ConfigurationTransforms TransformsApplied, TransformsFailed
StructuredConfigVariables FilesProcessed, FilesFailed, LeavesReplaced
WriteBootstrappedScript VariablesExported, OriginalScriptBytes, BootstrappedScriptBytes
ExecuteMainScript ExitCode, OutputVariablesCount
PreDeploy / PostDeploy / DeployFailed ExitCode, OutputVariablesCount

Every outcome carries DurationMs + Status (Succeeded / Skipped / Failed) + optional Message.

Key design choices

  • Opt-in IStepOutcomeAwareContext — same lightweight extension as IPathBasedExecutionContext / IFailureAwareExecutionContext. Existing callers unaffected.
  • StepStatus enum values pinned (Succeeded=1, Skipped=2, Failed=3) — binary-stable for serialised channels.
  • Snapshot in BuildRunScriptCommandResultStep — outcomes list copied (not aliased) into CommandExecutionResult so post-build mutations don't retroactively edit it.
  • DeployFailed non-zero exit emits Status=Failed but still doesn't re-throw (preserves H1 semantics — original execution failure stays canonical). Failure now visible via the outcome surface for analytics.
  • Skipped status fires for short-circuits (e.g. StructuredConfig with empty Targets glob) — UI shows "step ran, skipped" rather than the ambiguous absence-of-entry.

Test plan

  • Squid.Calamari.Tests: 463/463 (was 449; +14)
  • Squid.UnitTests: 5625/5625 (unchanged)
  • Solution build: 0 errors
  • Record-level: factory shape + record equality + enum value stability
  • Per-step: each step emits exactly one outcome with the documented metric keys
  • Skipped status path covered (StructuredConfig empty-targets)
  • Failed status without re-throw (DeployFailed hook itself fails)

Non-breaking guarantee

Surface Status
Existing wire literals Unchanged
Existing Console.WriteLine summary lines Preserved (additive only)
CommandExecutionResult ctor Additive stepOutcomes parameter with default null → empty list
Existing RunScriptCommand.ExecuteAsync callers Get the new StepOutcomes property on the result; reading is optional
SquidWeb Untouched

Merge request reports

Loading