Files
narratio/docs/roadmap/fix-publish-output-destination-resolution.md
Eric Rakestraw 442469b9f5
All checks were successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
Fix derived publish destinations with explicit requiredness
2026-09-21 03:06:13 +00:00

6.9 KiB

Bugfix Request: Preserve Publish Destination Derivation with Explicit Rules

Status: Implemented; release target v2.1.1

Summary

Narratio should successfully derive a publish destination when a valid publish.outputs rule omits dest, regardless of whether that rule explicitly sets required. Narratio v2.1.0 currently rejects this valid configuration in some cases with a ConfigKit resolve.unrecorded_change diagnostic.

This is a configuration-resolution bookkeeping defect, not a YAML formatting restriction. The fix should preserve the existing public configuration contract: operators may omit a derivable destination and may independently set required to either true or false.

Observed Behavior

A pipeline containing a rule equivalent to the following fails during native configuration loading:

publish:
  outputs: [{"source": "narratio.transcript.final", "required": true}]

Narratio reports:

error [resolve.unrecorded_change] /publish/outputs: application resolution changed a value without a covering record

The same failure is possible with ordinary block-style YAML when required is explicit and dest is omitted. Flow-style YAML is valid input and is not the cause of the defect.

The existing destination-derivation tests do not expose this case because their rules also omit required. Narratio then supplies the default value for required and records a change covering the complete publish.outputs sequence. That broader record happens to cover the destination mutation as well, masking the missing resolution record that occurs when required is already explicit.

Expected Contract

The current configuration contract remains unchanged:

  • publish.outputs[].source is required;
  • publish.outputs[].dest may be omitted when Narratio can derive it from the selected built-in or configured artifact source;
  • publish.outputs[].required defaults to true, but an operator may set it explicitly to either true or false; and
  • equivalent valid YAML representations have equivalent configuration semantics.

For example, both rules below should load and validate, retain their explicit requiredness, and receive the canonical derived destination:

publish:
  outputs:
    - source: narratio.transcript.final
      required: true
    - source: narratio.transcript.final_markdown
      required: false

Root Cause

ConfigKit intentionally treats sequences as atomic when comparing values before and after application resolution. Changing a field within one publish.outputs entry is therefore observed as a change to the /publish/outputs sequence.

Narratio currently derives each missing destination by recording a normalization at the individual publish.outputs[index].dest path. That child record does not cover the atomic parent-sequence change. A separate container-level record is currently created only when Narratio also defaults an omitted required value, so configurations with explicit required values lose that incidental coverage.

Make Narratio's publish-output resolution bookkeeping conform to ConfigKit's atomic-sequence contract. Whenever resolution derives one or more destinations inside publish.outputs, the recorded resolution changes must cover the /publish/outputs container. Destination derivation must not depend on an unrelated required default being applied in the same list.

The implementation may retain more specific provenance where useful, but it must provide valid container-level coverage for the sequence mutation. Do not change ConfigKit's sequence comparison semantics merely to accommodate this call site.

Scope

  • Correct destination derivation for publish rules that explicitly set required and omit a derivable dest.
  • Preserve explicit required: true and required: false values.
  • Preserve canonical destination derivation for built-in transcript sources and configured artifact sources.
  • Add focused regression coverage through Narratio's real configuration load and validation boundary.
  • Prepare a backward-compatible patch release after the fix passes the normal repository validation.

Non-goals

  • Changing the publish.outputs schema, defaults, source vocabulary, or destination derivation rules.
  • Requiring operators or configuration generators to supply dest when it is derivable.
  • Requiring operators to omit explicit required: true values.
  • Treating flow-style YAML or JSON-compatible YAML values as unsupported.
  • Changing ConfigKit's documented atomic treatment of sequences.
  • Adding storage, publication, or network integration behavior.

Regression Coverage

Add one focused package-level behavioral test that loads and validates a pipeline with omitted destinations and explicit requiredness. A representative list should include both required: true and required: false, then assert that:

  • loading and validation succeed;
  • each destination is derived from its source; and
  • each explicit required value is preserved.

Using the compact flow-style representation that exposed the integration failure is reasonable, while the existing block-style cases continue to cover omitted required. The test should exercise the public configuration behavior, not ConfigKit call choreography or Narratio's private recording mechanism.

No network service, object store, credential, or test double is required.

Target End State

All valid publish rules with derivable sources behave consistently across these cases:

dest required Result
omitted omitted destination derived; required defaults to true
omitted true destination derived; explicit true retained
omitted false destination derived; explicit false retained
explicit either value explicit destination and requiredness retained

narratio config validate, narratio config show, and ordinary pipeline loading accept the corrected cases without an unrecorded-change diagnostic. No operator configuration migration or workaround is required.

Release

This is a backward-compatible correction to documented configuration behavior and should be released as Narratio v2.1.1. The release note should identify the corrected explicit-required/derived-destination interaction and state that no special upgrade action is required.

Acceptance Criteria

  • The focused regression test fails against v2.1.0 behavior and passes after the fix.
  • Publish rules with explicit required: true or required: false and omitted derivable destinations load and validate successfully.
  • Derived destinations and explicit requiredness appear correctly in the safe effective configuration.
  • Existing destination derivation, publish validation, imports, profiles, and configuration inspection tests remain green.
  • The complete repository validation and release-candidate checks pass.
  • The correction is published as the immutable v2.1.1 tag with the required release note.