Add semantic configuration profile comparison

This commit is contained in:
2026-08-30 15:10:38 +00:00
parent dde7f76ecb
commit 4c57ace2f6
13 changed files with 847 additions and 25 deletions

View File

@@ -20,7 +20,7 @@ Top-level commands:
- `publish <session_id>`: force-run publish.
- `clean <session_id>` or `clean --all`: remove local work/spool state.
- `session <subcommand>`: session helper commands.
- `config <subcommand>`: validate or display resolved pipeline configuration.
- `config <subcommand>`: validate, display, source-trace, or compare resolved pipeline configuration.
Session subcommands:
@@ -78,12 +78,13 @@ Commands with additional positionals keep their command-specific order:
## Command Reference
### `config validate`, `config show`, and `config sources`
### `config validate`, `config show`, `config sources`, and `config diff`
```bash
narratio config validate [--config <pipeline.yml>] [--campaign <id> | --campaign-file <campaign.yml>] [--profile <name>]
narratio config show [--config <pipeline.yml>] [--campaign <id> | --campaign-file <campaign.yml>] [--profile <name>]
narratio config sources [--config <pipeline.yml>] [--campaign <id> | --campaign-file <campaign.yml>] [--profile <name>]
narratio config diff <left-profile> <right-profile> [--config <pipeline.yml>] [--campaign <id> | --campaign-file <campaign.yml>]
```
These commands resolve the selected profile, defaults, ordinary paths, and—if
@@ -112,6 +113,27 @@ legacy-player, and generated family ownership. A generated party member has
one family record and one party record at the same logical path. The output
never reads or prints secret values.
`config diff` resolves both supplied profile names from one parsed root source
set and compares their fully resolved, secret-free effective mappings. It does
not accept `--profile`; the two positional names must be distinct, declared
profiles. When party-driven families are present, both profiles must resolve to
the same selected campaign and party. Use `--campaign-file` if profile-specific
campaign configuration would otherwise select different files.
Equal profiles print `no differences`. Otherwise, sorted tab-separated records
use one of these forms, with compact JSON values:
```text
added <path> <right-value>
removed <path> <left-value>
changed <path> <left-value> <right-value>
```
Mappings are flattened to their logical field paths; lists remain one atomic
value. The command compares defaulted concrete artifacts and publish rules, not
profile names, source-file layout, or formatting. It succeeds when differences
are found, making it suitable for review and migration checks.
### `version`
```bash

View File

@@ -48,6 +48,9 @@ the durable copied session input.
sources` use the same `--config`, `--campaign`, `--campaign-file`, and
`--profile` selection rules as pipeline commands, but do not select, discover,
or load a session. They do not read credential values or create runtime state.
`narratio config diff <left-profile> <right-profile>` uses the same pipeline and
campaign selectors, resolves each named profile independently from one parsed
root source set, and does not accept a separate `--profile` flag.
Campaign selection is optional only when the resolved pipeline has no
`scriptorium.artifact_families`. When families are declared, Narratio selects a
@@ -60,9 +63,17 @@ composition and family declarations omitted. `config sources` prints a stable
source projection instead of effective values: root/import/profile/default
ownership plus campaign/party and generated-family records. Canonical derived
players trace to the party; a legacy configured players file is explicitly
marked as a legacy player source. The [CLI reference](cli.md#config-validate-config-show-and-config-sources)
marked as a legacy player source. The [CLI reference](cli.md#config-validate-config-show-config-sources-and-config-diff)
owns command syntax and output conventions.
`config diff` compares normalized field values rather than YAML text or source
ownership. It emits sorted `added`, `removed`, and `changed` records, uses
compact deterministic JSON values, treats lists atomically, and reports `no
differences` when the complete effective configurations are equal. Concrete
family members and generated publish rules participate after expansion; moving
an equal value between eligible root/import sources does not create a
difference.
### Identity segments
Campaign IDs (`campaign_id` and `default_campaign_id`), session IDs, previous

View File

@@ -53,7 +53,7 @@ a temporary downloaded session file therefore cannot invalidate the resolved
pipeline or campaign context.
The application also has a separate read-only inspection resolver for `config
validate` and `config show`. It uses the same production root/profile and
validate`, `config show`, and `config sources`. It uses the same production root/profile and
campaign selection functions, but never routes through session discovery,
remote-session download, secret loading, adapter composition, workspace
initialization, manifest access, or cleanup. A pipeline with retained artifact
@@ -79,6 +79,17 @@ derived players point to the party source, while legacy players retain a
dedicated legacy-player role. The application command only joins these sorted
records with selection metadata and never reparses configuration files.
`config diff` uses a paired profile loader that parses the root, imports, and
declared overlays once, then clones the additive base before independently
selecting, decoding, defaulting, and finalizing each profile. When campaign
resolution is needed, the command loads one selected campaign and party and
expands both effective pipelines from that same party value. The configuration
owner projects each normalized effective mapping into sorted logical paths;
mapping leaves are compared individually while sequence values remain atomic.
Values are compact deterministic JSON representations for command output, not
raw YAML fragments, ownership records, or secret material. A differing digest
with no projected difference is treated as an internal consistency error.
Campaign context construction also reads and classifies the campaign-owned
party source through `ParseParty`. A canonical party retains its raw bytes and
normalized roster in runtime-only `ResolvedParty` provenance, while a legacy

View File

@@ -75,6 +75,14 @@ and effective configuration digest. Status distinguishes the current resolved
value from the last value persisted in the session manifest, which helps
diagnose profile switches without changing resume authority.
Before switching an operational profile, compare its effective meaning with the
current selection through `narratio config diff <left-profile> <right-profile>`.
The command is read-only and succeeds whether it finds differences or not. Its
sorted records describe defaulted, expanded concrete configuration—not source
file layout—so it can be used to review model, artifact, and publish changes
without creating a session or run. Select the same campaign explicitly when
profiles could resolve different campaign paths; see the [CLI reference](cli.md#config-validate-config-show-config-sources-and-config-diff) for syntax and record format.
## Stage Execution and Continuation Behavior
Canonical stage order:

View File

@@ -1048,7 +1048,7 @@ generated configuration without leaking secrets or internal YAML machinery.
## Stage 19 — Semantic `config diff`
**Status: Pending**
**Status: Completed**
### Goal