A3: structured StepOutcome surfaced through CommandExecutionResult
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 asIPathBasedExecutionContext/IFailureAwareExecutionContext. Existing callers unaffected. -
StepStatusenum values pinned (Succeeded=1, Skipped=2, Failed=3) — binary-stable for serialised channels. -
Snapshot in
BuildRunScriptCommandResultStep— outcomes list copied (not aliased) intoCommandExecutionResultso post-build mutations don't retroactively edit it. -
DeployFailednon-zero exit emitsStatus=Failedbut 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 |