Prepare the v1.6.0 release
This commit is contained in:
@@ -22,4 +22,5 @@ See the [release procedure](../release.md) for creating a candidate and tag.
|
|||||||
When asynchronous publication succeeds, the corresponding Gitea release is the
|
When asynchronous publication succeeds, the corresponding Gitea release is the
|
||||||
canonical source for downloadable binaries and checksums.
|
canonical source for downloadable binaries and checksums.
|
||||||
|
|
||||||
|
- [v1.6.0](v1.6.0.md)
|
||||||
- [v1.5.0](v1.5.0.md)
|
- [v1.5.0](v1.5.0.md)
|
||||||
|
|||||||
74
docs/releases/v1.6.0.md
Normal file
74
docs/releases/v1.6.0.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Narratio v1.6.0
|
||||||
|
|
||||||
|
Narratio v1.6.0 makes large pipeline configurations easier to organize,
|
||||||
|
inspect, and vary while adding character-oriented artifact generation and a
|
||||||
|
guarded, reproducible release procedure.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Pipeline configuration can now be assembled from explicit additive imports and
|
||||||
|
a selected production or testing profile. Campaigns can own a canonical,
|
||||||
|
versioned party roster, and Scriptorium artifact families can expand one
|
||||||
|
definition into concrete per-character artifacts, dependencies, variables, and
|
||||||
|
publish rules.
|
||||||
|
|
||||||
|
New read-only configuration commands expose the fully resolved pipeline,
|
||||||
|
source provenance, semantic digest, and profile differences before a session is
|
||||||
|
run. Stage reuse now records configuration-sensitive semantic evidence so
|
||||||
|
profile or configuration changes cannot silently reuse incompatible work.
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
This is a backward-compatible feature release. Existing monolithic pipeline
|
||||||
|
files, concrete Scriptorium artifacts, publish rules, and configurations
|
||||||
|
without profiles remain supported. When profiles are declared and no explicit
|
||||||
|
profile is selected, Narratio uses the configured production default.
|
||||||
|
|
||||||
|
An unversioned party file plus a separate players file remains available as an
|
||||||
|
isolated legacy compatibility path, but it cannot drive artifact families. New
|
||||||
|
campaigns and new party-oriented features should use the `narratio.party.v1`
|
||||||
|
schema. Existing manifests remain readable; missing legacy semantic evidence
|
||||||
|
is treated as stale rather than trusted.
|
||||||
|
|
||||||
|
Narratio continues to consume the documented Notarius D&D pipeline contract.
|
||||||
|
The release workflow still cross-compiles Linux, macOS, and Windows binaries
|
||||||
|
for `amd64` and `arm64`; cross-compilation is not native runtime evidence for
|
||||||
|
macOS or Windows.
|
||||||
|
|
||||||
|
## Upgrade
|
||||||
|
|
||||||
|
No special action is required for existing monolithic configurations that do
|
||||||
|
not adopt profiles or artifact families. On the first run after upgrading,
|
||||||
|
stages recorded by older manifests may regenerate once because those records do
|
||||||
|
not contain the new semantic configuration evidence.
|
||||||
|
|
||||||
|
To adopt the new configuration model, use the maintained
|
||||||
|
`examples/production-testing` bundle as a migration reference: split stable
|
||||||
|
settings into explicit imports, define a production default and optional
|
||||||
|
testing profile, convert campaign party data to `narratio.party.v1`, remove the
|
||||||
|
separate players file, and then introduce character artifact families. Review
|
||||||
|
the result with `narratio config validate`, `config show`, `config sources`, and
|
||||||
|
`config diff` before running a session.
|
||||||
|
|
||||||
|
Campaign, session, previous-session, and run identifiers must satisfy the
|
||||||
|
documented portable identity grammar. Existing manifests or remote state with
|
||||||
|
unsafe legacy identifiers must be migrated before use.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Added root-owned, non-recursive additive pipeline imports with strict,
|
||||||
|
source-aware conflict detection.
|
||||||
|
- Added named pipeline profiles with an explicit production default and
|
||||||
|
deliberate command-line selection.
|
||||||
|
- Added `config validate`, `config show`, `config sources`, and `config diff`
|
||||||
|
for read-only inspection of resolved configuration and provenance.
|
||||||
|
- Added the strict `narratio.party.v1` campaign roster, including stable
|
||||||
|
character IDs, player and character names, optional aliases, and classes.
|
||||||
|
- Added deterministic players derivation and canonical party delivery to
|
||||||
|
downstream integrations.
|
||||||
|
- Added character-oriented artifact families, corresponding member
|
||||||
|
dependencies, family selection, and generated publish policies.
|
||||||
|
- Added semantic configuration fingerprints and stage-specific resume checks
|
||||||
|
across transcript and artifact stages.
|
||||||
|
- Added shared release candidate, asset build, and guarded tag-publication
|
||||||
|
scripts, with tag-triggered publication remaining asynchronous.
|
||||||
@@ -1,471 +0,0 @@
|
|||||||
# Release Procedure Upgrade Implementation Plan
|
|
||||||
|
|
||||||
## Purpose And Status
|
|
||||||
|
|
||||||
This document is the executable implementation plan for
|
|
||||||
[`release-procedure.md`](release-procedure.md). The feature roadmap owns the
|
|
||||||
accepted user intent, policy choices, required outcome, and responsibility
|
|
||||||
boundary. This plan translates that target into bounded implementation stages
|
|
||||||
suitable for one `gpt-5.6-terra` coding prompt apiece.
|
|
||||||
|
|
||||||
All five stages were completed in numeric order. This document is retained as
|
|
||||||
implementation history; current release behavior is owned by the checked-in
|
|
||||||
scripts and canonical release documentation. This plan superseded the completed
|
|
||||||
pipeline-configuration implementation plan that previously occupied this path;
|
|
||||||
that feature's implemented behavior is now owned by current canonical
|
|
||||||
documentation and code.
|
|
||||||
|
|
||||||
## Settled Implementation Decisions
|
|
||||||
|
|
||||||
The following decisions are final for this implementation:
|
|
||||||
|
|
||||||
- The synchronous release boundary is a successful push of the exact selected
|
|
||||||
tag ref to `origin`. Neither `scripts/release.sh` nor any instruction that
|
|
||||||
defines successful release completion may poll Woodpecker, query build
|
|
||||||
status, wait for a Gitea release, download assets, or verify asynchronous
|
|
||||||
publication.
|
|
||||||
- The supported command surfaces are exactly:
|
|
||||||
`scripts/build-release-assets.sh VERSION OUTPUT_DIR`,
|
|
||||||
`scripts/check-release-candidate.sh VERSION`, and
|
|
||||||
`scripts/release.sh VERSION`.
|
|
||||||
- All scripts are POSIX `sh`, use `set -eu`, derive the repository root from
|
|
||||||
their own checked-in location, and use narrowly named variables. They do not
|
|
||||||
rely on the caller's current directory and do not repurpose `HOME`,
|
|
||||||
`CODEX_HOME`, or common system-option variables.
|
|
||||||
- A private source-only `scripts/release-lib.sh` owns the stable semantic
|
|
||||||
version parser and shared fatal-error/reporting helpers. It has no behavior
|
|
||||||
merely from being sourced. Public scripts source it relative to their own
|
|
||||||
location so version syntax and error conventions do not drift.
|
|
||||||
- Stable versions match exactly
|
|
||||||
`v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)`. Prerelease and build
|
|
||||||
suffixes are rejected.
|
|
||||||
- `scripts/build-release-assets.sh` accepts exactly two arguments. Its output
|
|
||||||
path must be absolute, must not be `/` or the repository root, must not be a
|
|
||||||
symlink or non-directory, and must either not exist or be an empty directory.
|
|
||||||
The script creates the directory when absent, writes only its six named
|
|
||||||
assets and any narrowly scoped temporary smoke binary, and never recursively
|
|
||||||
clears a caller-supplied path.
|
|
||||||
- Official targets are CGO-disabled Linux, Darwin, and Windows builds for both
|
|
||||||
`amd64` and `arm64`. Filenames are
|
|
||||||
`narratio-VERSION-GOOS-GOARCH`, with `.exe` only on Windows. Builds use
|
|
||||||
`-trimpath`, `-s -w`, and
|
|
||||||
`-X gitea.maximumdirect.net/eric/narratio/internal/buildinfo.Version=VERSION`.
|
|
||||||
- The asset builder verifies `narratio version` on the current host. When the
|
|
||||||
host is one of the six official targets it executes that official asset;
|
|
||||||
otherwise it builds a separate temporary host smoke binary, executes it,
|
|
||||||
and removes only that file. Expected output is exactly
|
|
||||||
`narratio VERSION`.
|
|
||||||
- `scripts/check-release-candidate.sh` is read-only with respect to tracked
|
|
||||||
source and Git refs. It accepts exactly one version, validates the matching
|
|
||||||
note, performs repository and Go-module hygiene checks, runs every required
|
|
||||||
test/static/build/documentation/example check uncached where applicable,
|
|
||||||
verifies formatting and whitespace, and calls the shared asset builder in a
|
|
||||||
directory created with `mktemp -d`. Every Go command it owns runs with
|
|
||||||
`GOWORK=off`.
|
|
||||||
- The candidate checker deliberately does not require `main`, a clean checkout,
|
|
||||||
synchronized remote refs, or an unused tag. This allows it to validate a
|
|
||||||
candidate before commit and to run in tag CI's detached checkout. Those
|
|
||||||
mutable publication guards belong only to `scripts/release.sh`.
|
|
||||||
- `scripts/release.sh` accepts exactly one version, operates only against the
|
|
||||||
remote named `origin` and branch named `main`, and never accepts bypass,
|
|
||||||
force, alternate-remote, skip-check, or CI-wait flags.
|
|
||||||
- The publication script uses a two-phase guard. It checks cheap local and
|
|
||||||
upstream conditions before the potentially long candidate checker, then
|
|
||||||
fetches and rechecks the clean checkout, exact `HEAD == origin/main`, and
|
|
||||||
local/remote tag absence immediately before creating the tag. This prevents
|
|
||||||
a long validation run from publishing after the candidate or upstream tip
|
|
||||||
changed.
|
|
||||||
- Release tags remain explicitly unsigned lightweight tags. The script uses
|
|
||||||
`git -c tag.gpgSign=false tag VERSION COMMIT`, verifies that the ref type is
|
|
||||||
`commit` and resolves to the recorded commit, and pushes only
|
|
||||||
`refs/tags/VERSION:refs/tags/VERSION`. It never pushes `main` or uses
|
|
||||||
`git push --tags`.
|
|
||||||
- A failed push leaves the unpublished local tag intact for inspection. The
|
|
||||||
script never silently deletes or retries it. A successful push prints the
|
|
||||||
version and exact commit, returns zero immediately, and performs no later
|
|
||||||
network or CI action.
|
|
||||||
- Release-script behavioral tests use real temporary Git repositories and a
|
|
||||||
local bare `origin`, with a fixture-local stub candidate checker where a
|
|
||||||
full repository validation would add no confidence. They never touch the
|
|
||||||
developer's actual refs or any network service.
|
|
||||||
- Tag CI uses `golang:1.25.5` and
|
|
||||||
`woodpeckerci/plugin-release:0.3.1`. Its steps are named
|
|
||||||
`validate-release`, `build-release-assets`, and `publish-release` in that
|
|
||||||
dependency order.
|
|
||||||
- The release plugin explicitly sets title
|
|
||||||
`Narratio ${CI_COMMIT_TAG}`, note
|
|
||||||
`docs/releases/${CI_COMMIT_TAG}.md`, flattened SHA-256 checksums,
|
|
||||||
`file-exists: skip`, `overwrite: false`, and `prerelease: false`. Skipping an
|
|
||||||
existing remote release preserves idempotence and immutability; repairing a
|
|
||||||
failed published tag remains a new-patch-release operation.
|
|
||||||
- Release documentation describes optional later inspection but never makes
|
|
||||||
CI success, Gitea release creation, checksum download, or host asset execution
|
|
||||||
a required step after a successful tag push.
|
|
||||||
- This feature does not create the next release note, tag a version, push
|
|
||||||
`main`, or publish a release. Those actions occur only after this
|
|
||||||
implementation sprint is complete and a separate release candidate is
|
|
||||||
selected.
|
|
||||||
|
|
||||||
## Instructions For Every Stage
|
|
||||||
|
|
||||||
Before changing files in each stage:
|
|
||||||
|
|
||||||
1. Read `docs/development.md`, all documents under `docs/policy/`, this plan,
|
|
||||||
and the relevant portions of
|
|
||||||
[`release-procedure.md`](release-procedure.md).
|
|
||||||
2. Inspect the current scripts, Woodpecker workflows, release notes,
|
|
||||||
`internal/buildinfo`, `internal/app/version.go`, and focused repository
|
|
||||||
checks relevant to the stage. Prefer the codebase knowledge graph for Go
|
|
||||||
discovery and use text search for shell, YAML, and Markdown.
|
|
||||||
3. Confirm the worktree state and preserve unrelated changes.
|
|
||||||
4. Re-read the responsibility boundary before editing any publication command:
|
|
||||||
successful upstream tag push ends the synchronous release process.
|
|
||||||
|
|
||||||
During every stage:
|
|
||||||
|
|
||||||
- Keep public shell interfaces and file ownership exactly as settled above.
|
|
||||||
- Use the Go standard library and ordinary POSIX tools already available in the
|
|
||||||
builder images; do not add a shell framework, Go dependency, release SaaS,
|
|
||||||
or package-manager tool.
|
|
||||||
- Keep tests deterministic, offline, and free of real credentials and remote
|
|
||||||
infrastructure. Use `t.TempDir()` and local bare Git repositories for
|
|
||||||
publication behavior.
|
|
||||||
- Test consequential behavior at the narrowest stable boundary. Avoid tests
|
|
||||||
that merely snapshot complete script text or duplicate every shell branch.
|
|
||||||
- Make failures identify the responsible script, invalid value, or failed
|
|
||||||
command without printing environment dumps or secret values.
|
|
||||||
- Do not document future behavior outside `docs/roadmap/` until the stage that
|
|
||||||
makes that behavior usable. Update canonical current-behavior documentation
|
|
||||||
in the same stage that completes the release procedure.
|
|
||||||
- Format changed Go files, run `sh -n` on changed shell files, use focused
|
|
||||||
tests while iterating, and finish each stage with `git diff --check` plus the
|
|
||||||
stage's exit criteria.
|
|
||||||
- Do not create, delete, move, or push a real Narratio tag while implementing
|
|
||||||
or testing this feature.
|
|
||||||
|
|
||||||
## Stage 1 — Shared Release Library And Asset Builder
|
|
||||||
|
|
||||||
**Status: Completed**
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
Create the single safe implementation of version validation and official
|
|
||||||
release-asset construction without changing Git refs, CI, or current release
|
|
||||||
documentation.
|
|
||||||
|
|
||||||
### Required Work
|
|
||||||
|
|
||||||
1. Create `scripts/release-lib.sh` as a source-only POSIX library. Give its
|
|
||||||
functions a Narratio-specific prefix. At minimum it must provide:
|
|
||||||
- a fatal-error helper that prefixes errors with the calling release tool;
|
|
||||||
- exact stable-semver validation; and
|
|
||||||
- a helper for locating the repository root from a public script's absolute
|
|
||||||
or relative path without depending on the caller's working directory.
|
|
||||||
Keep the library private to the scripts; it is not a supported user-facing
|
|
||||||
API.
|
|
||||||
2. Create executable `scripts/build-release-assets.sh` with the exact
|
|
||||||
`VERSION OUTPUT_DIR` interface and output-directory contract in the settled
|
|
||||||
decisions. Reject missing/extra arguments, malformed versions, relative or
|
|
||||||
dangerously broad output paths, symlink/non-directory output, and a
|
|
||||||
nonempty output directory before building anything.
|
|
||||||
3. Build the six official binaries in deterministic target order. Use the
|
|
||||||
current command package and build-info linker symbol; do not duplicate or
|
|
||||||
change application version semantics.
|
|
||||||
4. Verify exact host version output as settled above. Ensure a failed build or
|
|
||||||
version mismatch is fatal and leaves no false success message. Temporary
|
|
||||||
smoke output must be inside the validated staging directory and removed
|
|
||||||
narrowly.
|
|
||||||
5. Add focused tests under a new `internal/releasecheck` test package. Protect
|
|
||||||
the meaningful asset-builder risks without making the default Go suite
|
|
||||||
compile all six targets repeatedly:
|
|
||||||
- argument and semver rejection;
|
|
||||||
- refusal of unsafe, symlink, non-directory, and nonempty destinations;
|
|
||||||
- creation only beneath an accepted empty destination; and
|
|
||||||
- exact naming/version behavior through one controlled host build or a
|
|
||||||
command shim, whichever is simpler and remains behavior-focused.
|
|
||||||
The operational six-target build is exercised directly in the stage exit
|
|
||||||
criteria and later by candidate validation.
|
|
||||||
|
|
||||||
### Exit Criteria
|
|
||||||
|
|
||||||
- `sh -n scripts/release-lib.sh scripts/build-release-assets.sh` succeeds.
|
|
||||||
- Focused `internal/releasecheck` tests pass.
|
|
||||||
- Running the asset builder with a synthetic stable version and a fresh
|
|
||||||
temporary output directory produces exactly six official assets and the
|
|
||||||
host version check succeeds.
|
|
||||||
- Invalid and unsafe output examples fail before creating release assets.
|
|
||||||
- `go test ./...`, `go vet ./...`, `go build ./...`, and
|
|
||||||
`git diff --check` succeed.
|
|
||||||
|
|
||||||
## Stage 2 — Central Release-Candidate Checker
|
|
||||||
|
|
||||||
**Status: Completed**
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
Create one read-only source-candidate validator that maintainers and tag CI can
|
|
||||||
run identically, eliminating drift between documented preparation and tagged
|
|
||||||
source validation.
|
|
||||||
|
|
||||||
### Required Work
|
|
||||||
|
|
||||||
1. Create executable `scripts/check-release-candidate.sh` with the exact
|
|
||||||
one-version interface. Source `release-lib.sh`, resolve and enter the
|
|
||||||
repository root, and validate the version before using it in a path.
|
|
||||||
2. Require `docs/releases/VERSION.md` to be nonempty, begin with the exact
|
|
||||||
`# Narratio VERSION` heading, and contain exact
|
|
||||||
`## Summary`, `## Compatibility`, `## Upgrade`, and `## Changes` headings.
|
|
||||||
Do not parse prose or impose historical-note requirements on other tags.
|
|
||||||
3. Enforce repository/module hygiene:
|
|
||||||
- exact Narratio module declaration;
|
|
||||||
- no tracked `go.work` or `go.work.sum`;
|
|
||||||
- no `vendor` directory;
|
|
||||||
- no single-line or block `replace` declaration in `go.mod`;
|
|
||||||
- `GOWORK=off go mod tidy -diff` leaves module files unchanged;
|
|
||||||
- all tracked `*.go` files are `gofmt` clean; and
|
|
||||||
- both working-tree and cached whitespace checks pass.
|
|
||||||
4. Run the complete Narratio candidate checks with `GOWORK=off`:
|
|
||||||
- `go test -count=1 ./...`;
|
|
||||||
- `go test -race -count=1 ./...`;
|
|
||||||
- `go vet ./...`;
|
|
||||||
- `go build ./...`;
|
|
||||||
- `go test -count=1 ./internal/doccheck`; and
|
|
||||||
- `go test -count=1 ./internal/config -run '^TestExamplesLoadAndValidate$'`.
|
|
||||||
5. Create one temporary directory with `mktemp -d`, install a trap that removes
|
|
||||||
only that verified directory, and invoke `build-release-assets.sh` with the
|
|
||||||
selected version. The candidate checker owns no independent target matrix,
|
|
||||||
filenames, linker flags, or version smoke logic.
|
|
||||||
6. Extend `internal/releasecheck` with lean behavioral coverage for the
|
|
||||||
candidate checker's guards. Use isolated fixture repositories and command
|
|
||||||
shims where needed so default tests do not recursively run the complete
|
|
||||||
Narratio suite. Cover malformed version/note, wrong module identity,
|
|
||||||
disallowed workspace/vendor/replace state, propagation of a failing owned
|
|
||||||
validation command, and evidence that owned Go commands receive
|
|
||||||
`GOWORK=off`.
|
|
||||||
7. Ensure the checker itself does not inspect the active branch, fetch a
|
|
||||||
remote, require a clean status, check tag availability, mutate refs, or
|
|
||||||
contact Woodpecker/Gitea. Those are deliberately outside this script's
|
|
||||||
contract.
|
|
||||||
|
|
||||||
### Exit Criteria
|
|
||||||
|
|
||||||
- `sh -n scripts/release-lib.sh scripts/build-release-assets.sh scripts/check-release-candidate.sh`
|
|
||||||
succeeds.
|
|
||||||
- Focused `internal/releasecheck` tests pass, including failure propagation.
|
|
||||||
- A disposable fixture with a compliant synthetic release note exercises the
|
|
||||||
candidate checker successfully; malformed fixtures fail with actionable
|
|
||||||
script-owned errors.
|
|
||||||
- The repository-wide test, race, vet, build, documentation, example,
|
|
||||||
formatting, module-tidiness, and whitespace checks all pass independently.
|
|
||||||
- No Git ref, tracked file, or external service is mutated by the checker.
|
|
||||||
|
|
||||||
## Stage 3 — Guarded Tag Publication Command
|
|
||||||
|
|
||||||
**Status: Completed**
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
Provide one safe, non-interactive command that publishes exactly one guarded
|
|
||||||
lightweight tag and stops immediately when the upstream Git push succeeds.
|
|
||||||
|
|
||||||
### Required Work
|
|
||||||
|
|
||||||
1. Create executable `scripts/release.sh` with the exact one-version interface.
|
|
||||||
Source the shared library and reject invalid arguments before running Git
|
|
||||||
operations.
|
|
||||||
2. Implement the first cheap guard phase:
|
|
||||||
- require branch `main`;
|
|
||||||
- require an empty `git status --porcelain` result;
|
|
||||||
- fetch `origin main --tags`;
|
|
||||||
- record `HEAD^{commit}` as the immutable candidate for this attempt;
|
|
||||||
- require it to equal `origin/main^{commit}`;
|
|
||||||
- require the matching note to exist in that candidate; and
|
|
||||||
- reject an existing local or exact upstream tag.
|
|
||||||
3. Invoke `scripts/check-release-candidate.sh VERSION` without suppressing its
|
|
||||||
output or weakening any check.
|
|
||||||
4. Implement the second guard phase immediately after validation:
|
|
||||||
- refetch `origin main --tags`;
|
|
||||||
- require the checkout still to be clean and on `main`;
|
|
||||||
- require `HEAD` and `origin/main` still to equal the originally recorded
|
|
||||||
candidate commit; and
|
|
||||||
- recheck exact local and upstream tag absence.
|
|
||||||
Do not silently select a newer commit or restart validation.
|
|
||||||
5. Create the explicitly unsigned lightweight tag against the recorded commit,
|
|
||||||
verify its object type and resolved commit, show a concise local summary,
|
|
||||||
and push only its fully qualified tag ref to `origin`.
|
|
||||||
6. On push success, print one concise completion message containing the tag and
|
|
||||||
commit and exit zero immediately. There must be no commands after the push
|
|
||||||
that query CI, Gitea, a release API, assets, or checksums. Do not sleep or
|
|
||||||
retry waiting for asynchronous state.
|
|
||||||
7. On failure before tag creation, leave refs unchanged. On failure after local
|
|
||||||
tag creation, retain the local tag and return a clear instruction that it is
|
|
||||||
unpublished and requires inspection. Never automatically delete, move,
|
|
||||||
force-push, or reuse a tag.
|
|
||||||
8. Add behavioral tests in `internal/releasecheck` using temporary working
|
|
||||||
repositories and local bare `origin` repositories. Put a successful stub
|
|
||||||
candidate checker at the fixture's expected checked-in path so tests isolate
|
|
||||||
tag policy rather than rerun compilation. Cover at least:
|
|
||||||
- wrong branch and dirty checkout rejection;
|
|
||||||
- local `HEAD` not equal to `origin/main`;
|
|
||||||
- candidate-checker failure;
|
|
||||||
- existing local and upstream tag rejection;
|
|
||||||
- upstream `main` changing between the two guard phases;
|
|
||||||
- successful creation of a lightweight remote tag pointing to the exact
|
|
||||||
candidate commit;
|
|
||||||
- push failure retaining the local tag; and
|
|
||||||
- absence of any pushed branch or unrelated tag.
|
|
||||||
Tests must never use the real repository's `origin` or network.
|
|
||||||
|
|
||||||
### Exit Criteria
|
|
||||||
|
|
||||||
- All release scripts pass `sh -n`.
|
|
||||||
- Focused publication tests pass and prove the exact remote-tag outcome and
|
|
||||||
failure behavior.
|
|
||||||
- A success-path test demonstrates that the release command returns as soon as
|
|
||||||
the local bare-remote tag push succeeds; it performs no CI/Gitea follow-up.
|
|
||||||
- `go test ./...`, `go test -race ./...`, `go vet ./...`, `go build ./...`, and
|
|
||||||
`git diff --check` succeed.
|
|
||||||
- No real Narratio tag or remote ref is created or changed.
|
|
||||||
|
|
||||||
## Stage 4 — Asynchronous Woodpecker Release Integration
|
|
||||||
|
|
||||||
**Status: Completed**
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
Make tag CI consume the shared candidate and asset contracts while preserving
|
|
||||||
strict validation-before-publication ordering and complete independence from
|
|
||||||
the synchronous tag command.
|
|
||||||
|
|
||||||
### Required Work
|
|
||||||
|
|
||||||
1. Rewrite `.woodpecker/release.yml` around three explicit steps:
|
|
||||||
- `validate-release` in `golang:1.25.5`, invoking
|
|
||||||
`./scripts/check-release-candidate.sh "$CI_COMMIT_TAG"`;
|
|
||||||
- `build-release-assets` in `golang:1.25.5`, depending on
|
|
||||||
`validate-release`, requiring a fresh absent/empty absolute `$PWD/dist`,
|
|
||||||
and invoking the shared asset builder; and
|
|
||||||
- `publish-release`, depending on `build-release-assets` and using
|
|
||||||
`woodpeckerci/plugin-release:0.3.1`.
|
|
||||||
Remove the redundant inline target matrix, validation command list, and
|
|
||||||
separate cross-build step from the release workflow.
|
|
||||||
2. Configure publication exactly as settled: the existing release-token
|
|
||||||
secret, `dist/narratio-*` files, explicit title and tagged note, flattened
|
|
||||||
`SHA256SUMS`, `file-exists: skip`, no overwrite, and no prerelease.
|
|
||||||
3. Preserve the tag-only event trigger. Do not add a pipeline callback, status
|
|
||||||
endpoint, wait command, or any coupling from `scripts/release.sh` to this
|
|
||||||
workflow.
|
|
||||||
4. Update `internal/doccheck/doccheck_test.go` so the dependency assertion names
|
|
||||||
`validate-release` and still proves transitively that publication cannot run
|
|
||||||
after failed validation. Extend its minimal YAML model only as needed to
|
|
||||||
protect consequential release invariants:
|
|
||||||
- the validator calls the shared checker;
|
|
||||||
- asset publication depends on validated shared construction;
|
|
||||||
- the release plugin image is pinned;
|
|
||||||
- title and note use the selected tag; and
|
|
||||||
- overwrite/prerelease remain disabled.
|
|
||||||
Avoid snapshotting the whole workflow or duplicating every plugin setting.
|
|
||||||
5. Confirm `.woodpecker/verify.yml` and `.woodpecker/shuffle.yml` remain normal
|
|
||||||
push/PR and scheduled validation. Do not make either wait for or invoke a
|
|
||||||
release operation.
|
|
||||||
|
|
||||||
### Exit Criteria
|
|
||||||
|
|
||||||
- The Woodpecker YAML parses and the focused `internal/doccheck` workflow
|
|
||||||
dependency/invariant tests pass.
|
|
||||||
- The release workflow has one target matrix owner and one candidate-check
|
|
||||||
owner, both under `scripts/`.
|
|
||||||
- `publish-release` has a dependency path to `validate-release`; no failure in
|
|
||||||
validation or asset building can publish.
|
|
||||||
- Static inspection confirms `scripts/release.sh` has no reference to
|
|
||||||
Woodpecker, Gitea release status, or CI polling.
|
|
||||||
- `go test ./...`, `go test -race ./...`, `go vet ./...`, `go build ./...`,
|
|
||||||
documentation/example checks, and `git diff --check` succeed.
|
|
||||||
|
|
||||||
## Stage 5 — Canonical Release Documentation And Final Audit
|
|
||||||
|
|
||||||
**Status: Completed**
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
Document the now-implemented maintainer workflow in one canonical location,
|
|
||||||
make it discoverable, and perform a complete offline acceptance audit without
|
|
||||||
creating or publishing a real release.
|
|
||||||
|
|
||||||
### Required Work
|
|
||||||
|
|
||||||
1. Create `docs/release.md` as the canonical current-behavior procedure. It
|
|
||||||
must include:
|
|
||||||
- stable SemVer selection for a post-`v1.0.0` project;
|
|
||||||
- the exact future release-note structure;
|
|
||||||
- candidate preparation and ordinary `main` push expectations;
|
|
||||||
- direct use of `scripts/check-release-candidate.sh`;
|
|
||||||
- direct use and complete safety contract of `scripts/release.sh`;
|
|
||||||
- lightweight-tag immutability and correction through a new patch version;
|
|
||||||
- the explicit statement that successful upstream tag push completes the
|
|
||||||
release command; and
|
|
||||||
- a clearly optional asynchronous inspection section, separated from the
|
|
||||||
release steps and stating that a human may inspect CI/Gitea later but an
|
|
||||||
automated releaser must not wait for it.
|
|
||||||
Do not embed a second implementation of script guards as a long shell block;
|
|
||||||
the scripts own volatile mechanics and the document owns the maintainer
|
|
||||||
workflow and policy.
|
|
||||||
2. Update `docs/development.md` with a task-guide row pointing release work to
|
|
||||||
`docs/release.md`. Keep its validation summary concise and link to the
|
|
||||||
release procedure/checker rather than duplicating the candidate command
|
|
||||||
list.
|
|
||||||
3. Update `docs/policy/documentation.md` to assign canonical ownership for the
|
|
||||||
maintainer release procedure and version-matched historical notes. Preserve
|
|
||||||
the rule that release notes link to, rather than replace, current contract
|
|
||||||
documentation.
|
|
||||||
4. Update `docs/releases/README.md` to explain forward-looking note structure,
|
|
||||||
retain its existing `v1.5.0` entry, and identify the Gitea release collection
|
|
||||||
as the binary/checksum source when asynchronous publication succeeds. Do
|
|
||||||
not backfill or rewrite historical notes.
|
|
||||||
5. Review other current documentation for claims that release completion waits
|
|
||||||
on CI. Remove or link any conflicting duplication. Do not add release
|
|
||||||
mechanics to operations, CLI, architecture, or integration documents unless
|
|
||||||
they already own a directly affected current contract.
|
|
||||||
6. Mark `docs/roadmap/release-procedure.md` implemented only after scripts,
|
|
||||||
tests, CI, and canonical documentation match its target. Keep implementation
|
|
||||||
status in roadmap documents; current-behavior claims belong in
|
|
||||||
`docs/release.md` and the policy/developer routing documents.
|
|
||||||
7. Perform a final behavior and safety audit:
|
|
||||||
- inspect all scripts for unsafe path deletion, unresolved variables,
|
|
||||||
accidental secret output, broad ref pushes, force options, and commands
|
|
||||||
after the successful tag push;
|
|
||||||
- run all release-script behavioral tests against temporary local remotes;
|
|
||||||
- run the asset builder directly with a synthetic stable version and a
|
|
||||||
fresh temporary output directory, then verify the six filenames and host
|
|
||||||
version output;
|
|
||||||
- validate candidate-checker success in its isolated fixture and every
|
|
||||||
important failure class without creating a real note or tag;
|
|
||||||
- confirm the release workflow reads the note from the tagged tree and
|
|
||||||
cannot publish before validation; and
|
|
||||||
- confirm no test or documentation instruction requires live CI, Gitea,
|
|
||||||
credentials, or paid/external adapters.
|
|
||||||
|
|
||||||
### Exit Criteria
|
|
||||||
|
|
||||||
- All local Markdown links and Woodpecker dependency checks pass.
|
|
||||||
- `sh -n scripts/release-lib.sh scripts/build-release-assets.sh scripts/check-release-candidate.sh scripts/release.sh`
|
|
||||||
succeeds.
|
|
||||||
- Release-script tests and the direct six-target asset build pass without
|
|
||||||
touching real refs or external services.
|
|
||||||
- The full repository validation set from `docs/development.md` passes:
|
|
||||||
- `go test ./...`;
|
|
||||||
- `go test -race ./...`;
|
|
||||||
- `go vet ./...`;
|
|
||||||
- `go build ./...`;
|
|
||||||
- `go test ./internal/doccheck`; and
|
|
||||||
- `go test ./internal/config -run '^TestExamplesLoadAndValidate$'`.
|
|
||||||
- `go mod tidy -diff`, tracked-file `gofmt` inspection,
|
|
||||||
`git diff --check`, and `git diff --cached --check` succeed.
|
|
||||||
- The repository contains no synthetic release note, local test tag, build
|
|
||||||
asset, credential, temporary repository, or other generated test residue.
|
|
||||||
- No real release is tagged or pushed during this implementation plan.
|
|
||||||
|
|
||||||
## Open Questions
|
|
||||||
|
|
||||||
None. The feature roadmap and settled decisions above are sufficient to
|
|
||||||
implement the release procedure without additional product or policy choices.
|
|
||||||
@@ -1,739 +0,0 @@
|
|||||||
# Pipeline Configuration Ergonomics
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Accepted target state. This document owns the intended feature scope, user
|
|
||||||
intent, policy choices, and target end state until the behavior is implemented.
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Make Narratio's pipeline configuration easier to organize, vary, inspect, and
|
|
||||||
reuse without weakening its strict configuration contract or turning it into a
|
|
||||||
general-purpose configuration or workflow language.
|
|
||||||
|
|
||||||
The feature should let operators:
|
|
||||||
|
|
||||||
- split a large pipeline configuration into explicitly imported partial files;
|
|
||||||
- maintain one production-default pipeline plus a selectable testing profile;
|
|
||||||
- keep stable settings separate from frequently edited models and artifacts;
|
|
||||||
- define a character-oriented artifact workflow once and expand it for every
|
|
||||||
character in the selected campaign;
|
|
||||||
- reuse one campaign-owned party roster across multiple artifact families and
|
|
||||||
the Notarius integration; and
|
|
||||||
- inspect and compare the fully resolved configuration before running a
|
|
||||||
session.
|
|
||||||
|
|
||||||
All composition and expansion must produce the same concrete, validated
|
|
||||||
`PipelineConfig` and concrete configured-artifact map consumed by the existing
|
|
||||||
stage, planning, manifest, artifact, and adapter boundaries.
|
|
||||||
|
|
||||||
## User Intent
|
|
||||||
|
|
||||||
Pipeline configuration currently combines long-lived platform and transcript
|
|
||||||
settings with model choices, prompt-backed artifact definitions, and publish
|
|
||||||
rules that change frequently during development. Keeping all of those concerns
|
|
||||||
in one file makes edits noisy and makes production/testing variation difficult
|
|
||||||
to review.
|
|
||||||
|
|
||||||
Character-specific artifacts introduce a second kind of repetition. Several
|
|
||||||
artifact families, including character meta-analysis and character-specific
|
|
||||||
item tracking, should execute once per campaign character. Their prompt,
|
|
||||||
inputs, dependencies, and output shape are shared, while player, character,
|
|
||||||
class, alias, and output identity vary by party member. Those campaign facts do
|
|
||||||
not belong in a global pipeline definition.
|
|
||||||
|
|
||||||
The desired model is therefore:
|
|
||||||
|
|
||||||
```text
|
|
||||||
pipeline.yml + explicit imports + one selected profile
|
|
||||||
│
|
|
||||||
campaign.yml + party.yml ─┤
|
|
||||||
▼
|
|
||||||
resolved effective configuration
|
|
||||||
│
|
|
||||||
expand party-driven families
|
|
||||||
│
|
|
||||||
defaults and strict validation
|
|
||||||
▼
|
|
||||||
existing concrete pipeline and artifact model
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architectural Constraints
|
|
||||||
|
|
||||||
The feature must preserve the configuration and orchestration policies in
|
|
||||||
[`docs/policy/architecture.md`](../policy/architecture.md):
|
|
||||||
|
|
||||||
- YAML remains strict and rejects unknown fields.
|
|
||||||
- Defaults remain centralized and are applied exactly once.
|
|
||||||
- Composition and expansion are deterministic and testable.
|
|
||||||
- Session templating remains narrow; this feature does not introduce arbitrary
|
|
||||||
expressions, scripting, or a general template language.
|
|
||||||
- The canonical stage sequence and fixed invalidation relation remain
|
|
||||||
application-owned and non-configurable.
|
|
||||||
- Stages receive resolved Narratio-owned configuration and do not implement
|
|
||||||
import, profile, or family semantics independently.
|
|
||||||
- External tools continue to own their private runtime configuration and
|
|
||||||
defaults.
|
|
||||||
- Raw secrets remain indirect and are never introduced into composed YAML,
|
|
||||||
effective-configuration output, manifests, or diagnostics.
|
|
||||||
|
|
||||||
Configuration composition belongs to `internal/config`. CLI selection and
|
|
||||||
production loading belong to `internal/app`. Party parsing must be a
|
|
||||||
Narratio-owned campaign-input contract. Artifact-family expansion must finish
|
|
||||||
before existing configured-artifact validation, catalog construction,
|
|
||||||
planning, reconciliation, or execution begins.
|
|
||||||
|
|
||||||
## Configuration Imports
|
|
||||||
|
|
||||||
### Root document
|
|
||||||
|
|
||||||
Keep `pipeline.yml` as the selected pipeline entry point. Add an optional
|
|
||||||
top-level composition envelope with explicitly ordered imports:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
composition:
|
|
||||||
imports:
|
|
||||||
- conf.d/platform.yml
|
|
||||||
- conf.d/transcripts.yml
|
|
||||||
- conf.d/extraction.yml
|
|
||||||
- conf.d/artifacts.yml
|
|
||||||
- conf.d/publish.yml
|
|
||||||
|
|
||||||
default_profile: production
|
|
||||||
|
|
||||||
profiles:
|
|
||||||
production:
|
|
||||||
overlay: profiles/production.yml
|
|
||||||
testing:
|
|
||||||
overlay: profiles/testing.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
An existing monolithic `pipeline.yml` without `composition` remains valid and
|
|
||||||
retains its current behavior. The root document may contain ordinary pipeline
|
|
||||||
fields alongside `composition`, allowing incremental migration.
|
|
||||||
|
|
||||||
`imports` is the public term. Imported files are partial pipeline
|
|
||||||
configuration documents rather than independently runnable pipelines.
|
|
||||||
|
|
||||||
### Import boundaries
|
|
||||||
|
|
||||||
- Only the root pipeline document may declare `composition`, imports, or
|
|
||||||
profiles. Imported and overlay documents cannot import other documents.
|
|
||||||
- Every import is explicit. Narratio does not automatically scan `conf.d` or
|
|
||||||
interpret unlisted files.
|
|
||||||
- Import paths are relative to the root pipeline file's directory, must remain
|
|
||||||
confined beneath that directory, and must resolve to supported regular YAML
|
|
||||||
files. Absolute paths, traversal, non-regular files, and cycles are rejected.
|
|
||||||
- All ordinary relative pipeline paths retain one stable base: the root
|
|
||||||
pipeline file's directory. Moving a field between imported files must not
|
|
||||||
silently change the meaning of its relative path.
|
|
||||||
- The declared import order is retained for diagnostics and provenance, but it
|
|
||||||
is not an implicit precedence mechanism between base imports.
|
|
||||||
- Composition metadata is removed before the effective mapping is decoded as
|
|
||||||
strict `PipelineConfig` data.
|
|
||||||
|
|
||||||
### Additive merge contract
|
|
||||||
|
|
||||||
Base configuration from the root document and its imports is additive:
|
|
||||||
|
|
||||||
- mappings merge recursively when their child fields are disjoint;
|
|
||||||
- two files may therefore contribute different entries beneath a shared map
|
|
||||||
such as `scriptorium.artifacts`;
|
|
||||||
- defining the same final field or keyed entry in more than one base source is
|
|
||||||
an error, even when the values are identical;
|
|
||||||
- lists are atomic values and cannot be contributed more than once at the same
|
|
||||||
base configuration path;
|
|
||||||
- mapping/scalar, mapping/list, and other incompatible type collisions are
|
|
||||||
errors; and
|
|
||||||
- duplicate YAML keys within one source remain errors.
|
|
||||||
|
|
||||||
Errors must identify the complete configuration path and every source file
|
|
||||||
that claims it. This contract prevents an import reorder or newly added file
|
|
||||||
from silently changing production behavior.
|
|
||||||
|
|
||||||
Composition must operate on a presence-aware YAML representation before Go
|
|
||||||
struct decoding. Merging already decoded structs is not sufficient because it
|
|
||||||
cannot reliably distinguish omission from explicitly configured `false`, zero,
|
|
||||||
an empty collection, or another meaningful zero value.
|
|
||||||
|
|
||||||
## Named Profiles
|
|
||||||
|
|
||||||
### Selection and precedence
|
|
||||||
|
|
||||||
Exactly one named profile may be active for an invocation. A composition root
|
|
||||||
may define `default_profile`; the maintained production-shaped configuration
|
|
||||||
uses `production`. Add `--profile <name>` to the common configuration flags for
|
|
||||||
every command that loads pipeline configuration:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
narratio run SESSION
|
|
||||||
narratio run SESSION --profile testing
|
|
||||||
narratio session plan SESSION --profile testing
|
|
||||||
narratio regenerate-artifacts SESSION --profile testing
|
|
||||||
```
|
|
||||||
|
|
||||||
The explicit CLI value overrides `default_profile`. An unknown profile, a
|
|
||||||
profile selection against a configuration that defines no such profile, an
|
|
||||||
empty profile name, or a default naming an undefined profile is an error before
|
|
||||||
stage composition. Do not add an environment-variable selector in this
|
|
||||||
feature; profile selection should remain visible in the command or root
|
|
||||||
configuration.
|
|
||||||
|
|
||||||
Only one profile can be selected. Profiles cannot extend other profiles, and
|
|
||||||
callers cannot stack several profiles. A single overlay provides the required
|
|
||||||
production/testing variation without creating user-programmable precedence.
|
|
||||||
|
|
||||||
### Overlay semantics
|
|
||||||
|
|
||||||
The selected profile is the sole intentional override layer:
|
|
||||||
|
|
||||||
- mappings recursively merge with the assembled base mapping;
|
|
||||||
- scalar and boolean leaves replace base leaves;
|
|
||||||
- lists replace the complete base list rather than concatenate implicitly;
|
|
||||||
- keyed maps such as configured artifacts merge by key, allowing a testing
|
|
||||||
profile to add experimental artifacts;
|
|
||||||
- an existing artifact can be disabled explicitly with `enabled: false`;
|
|
||||||
- an overlay cannot change a value's YAML kind incompatibly; and
|
|
||||||
- the fully overlaid result undergoes the same strict decode, defaults, path
|
|
||||||
resolution, expansion, and validation as a monolithic configuration.
|
|
||||||
|
|
||||||
Generic deletion syntax is outside this feature. Shared definitions should
|
|
||||||
live in base imports, while production-only and testing-only definitions live
|
|
||||||
in their corresponding overlays. Existing enable/disable fields and complete
|
|
||||||
list replacement cover the required cases without a YAML patch language.
|
|
||||||
|
|
||||||
### Effective identity and provenance
|
|
||||||
|
|
||||||
Every loaded configuration must retain bounded, non-secret provenance:
|
|
||||||
|
|
||||||
- the root pipeline path;
|
|
||||||
- the selected profile name, including whether it came from the default or
|
|
||||||
CLI;
|
|
||||||
- the ordered imported and overlay source paths;
|
|
||||||
- a deterministic digest of the normalized effective pipeline configuration;
|
|
||||||
and
|
|
||||||
- enough source ownership information to explain composition errors and
|
|
||||||
effective values.
|
|
||||||
|
|
||||||
Run reporting and invocation state should identify the selected profile and
|
|
||||||
effective configuration digest. The profile name is provenance, not a blanket
|
|
||||||
cache key: changing profiles must invalidate only work whose result-affecting
|
|
||||||
effective configuration changed.
|
|
||||||
|
|
||||||
## Canonical Campaign Party Contract
|
|
||||||
|
|
||||||
### Ownership and schema
|
|
||||||
|
|
||||||
Make `party.yml` a strict, versioned Narratio campaign-input contract. A
|
|
||||||
canonical file has this shape:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
schema_version: narratio.party.v1
|
|
||||||
|
|
||||||
characters:
|
|
||||||
arannis:
|
|
||||||
player:
|
|
||||||
name: Eric
|
|
||||||
character:
|
|
||||||
name: Arannis
|
|
||||||
alias:
|
|
||||||
- Ari
|
|
||||||
- The Grey Owl
|
|
||||||
classes:
|
|
||||||
- name: wizard
|
|
||||||
level: 8
|
|
||||||
|
|
||||||
brenna:
|
|
||||||
player:
|
|
||||||
name: Jane
|
|
||||||
character:
|
|
||||||
name: Brenna
|
|
||||||
classes:
|
|
||||||
- name: paladin
|
|
||||||
level: 6
|
|
||||||
- name: warlock
|
|
||||||
level: 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The contract has these semantics:
|
|
||||||
|
|
||||||
- `schema_version` is required and must equal `narratio.party.v1`.
|
|
||||||
- `characters` is a non-empty mapping.
|
|
||||||
- Each mapping key is the stable `character_id` and must satisfy the existing
|
|
||||||
configured-artifact key grammar.
|
|
||||||
- `player.name` and `character.name` are required, non-empty display strings.
|
|
||||||
- `character.alias` is optional and accepts a list of zero or more non-empty
|
|
||||||
alias strings. The singular field spelling `alias` is intentional.
|
|
||||||
- Leading or trailing whitespace and control characters in names, aliases, and
|
|
||||||
class names are rejected rather than silently normalized.
|
|
||||||
- A character alias cannot equal that character's primary name, another alias,
|
|
||||||
or another character's primary name or alias under case-insensitive
|
|
||||||
comparison. This keeps party-member grounding unambiguous. Player names may
|
|
||||||
repeat because one player may control more than one character.
|
|
||||||
- `character.classes` is required and non-empty. Every entry has a required
|
|
||||||
free-form `name` and an optional positive integer `level`.
|
|
||||||
- Class names are not restricted to a Narratio-owned D&D enumeration.
|
|
||||||
- Duplicate class names for one character are rejected case-insensitively.
|
|
||||||
- Class order and alias order are preserved.
|
|
||||||
- Narratio derives a deterministic class summary such as
|
|
||||||
`paladin 6 / warlock 2` and an alias summary that joins declared aliases with
|
|
||||||
`, ` in their declared order.
|
|
||||||
- Unknown fields and trailing YAML documents are rejected.
|
|
||||||
|
|
||||||
The stable `character_id`, rather than a display name, owns generated artifact
|
|
||||||
identity. Changing a display name, alias, class, or level retains that identity
|
|
||||||
and changes the relevant semantic inputs. Changing the mapping key is an
|
|
||||||
intentional remove-and-add operation.
|
|
||||||
|
|
||||||
### Load and prepare behavior
|
|
||||||
|
|
||||||
The selected campaign's effective `party_file` resolves relative to
|
|
||||||
`campaign.yml`. Narratio must load and validate canonical party data during
|
|
||||||
combined pipeline/campaign resolution so `session plan`, configuration
|
|
||||||
inspection, and artifact-family expansion see the same roster before stage
|
|
||||||
execution.
|
|
||||||
|
|
||||||
`prepare` continues to materialize the canonical document as
|
|
||||||
`inputs/party.yml`, record its source and content identity, and expose it as
|
|
||||||
`narratio.input.party`. The same canonical file is passed unchanged to
|
|
||||||
Notarius's `party` reference slot, which currently accepts YAML reference
|
|
||||||
material without imposing a competing roster schema.
|
|
||||||
|
|
||||||
### Players projection and compatibility
|
|
||||||
|
|
||||||
The canonical party document becomes the eventual single source of truth for
|
|
||||||
player-to-character relationships. Narratio derives a deterministic,
|
|
||||||
documented players-only YAML projection during prepare, materializes it at
|
|
||||||
`inputs/players.yml`, records its content identity, and exposes it through the
|
|
||||||
existing `narratio.input.players` source. This preserves the separate Notarius
|
|
||||||
`players` reference and existing Scriptorium source without requiring every
|
|
||||||
campaign to maintain duplicate facts.
|
|
||||||
|
|
||||||
The projection uses this versioned shape, with entries sorted by stable
|
|
||||||
character ID:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
schema_version: narratio.players.v1
|
|
||||||
players:
|
|
||||||
- name: Eric
|
|
||||||
character:
|
|
||||||
id: arannis
|
|
||||||
name: Arannis
|
|
||||||
alias:
|
|
||||||
- Ari
|
|
||||||
- The Grey Owl
|
|
||||||
```
|
|
||||||
|
|
||||||
There is one projection entry per character rather than one grouped entry per
|
|
||||||
display player name. This permits one player to control several characters and
|
|
||||||
does not conflate distinct players who happen to share a display name.
|
|
||||||
|
|
||||||
Backward compatibility must be narrow and removable:
|
|
||||||
|
|
||||||
- an unversioned legacy party file continues to be treated as opaque reference
|
|
||||||
material when the existing `players_file` is also configured;
|
|
||||||
- legacy mode preserves the current prepared `party` and `players` behavior
|
|
||||||
but cannot drive artifact-family expansion;
|
|
||||||
- configuring a party-driven artifact family with a legacy roster produces a
|
|
||||||
clear migration error;
|
|
||||||
- canonical `narratio.party.v1` mode derives players and rejects a separate
|
|
||||||
`players_file`, preventing contradictory authorities;
|
|
||||||
- parsing, validation, documentation, and tests for legacy mode live behind a
|
|
||||||
clearly identified compatibility boundary with comments stating that it is
|
|
||||||
intended for removal after migration; and
|
|
||||||
- the maintained examples migrate to canonical mode and demonstrate no
|
|
||||||
separate players file.
|
|
||||||
|
|
||||||
The derived players projection is a durable integration contract and must be
|
|
||||||
documented under `docs/integrations/` rather than left as incidental generated
|
|
||||||
YAML.
|
|
||||||
|
|
||||||
## Party-Driven Artifact Families
|
|
||||||
|
|
||||||
### Configuration model
|
|
||||||
|
|
||||||
Add `scriptorium.artifact_families`, separate from the existing concrete
|
|
||||||
`scriptorium.artifacts` map:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
scriptorium:
|
|
||||||
artifact_families:
|
|
||||||
character_meta:
|
|
||||||
enabled: true
|
|
||||||
for_each: party.characters
|
|
||||||
prompt_id: dnd.character_meta
|
|
||||||
profile_id: production
|
|
||||||
output_path_pattern: artifacts/characters/{character_id}/meta.md
|
|
||||||
inputs:
|
|
||||||
transcript:
|
|
||||||
source: narratio.transcript.final_trimmed
|
|
||||||
required: true
|
|
||||||
member_vars:
|
|
||||||
player_name: player.name
|
|
||||||
character_name: character.name
|
|
||||||
character_class: character.class_summary
|
|
||||||
character_aliases: character.alias_summary
|
|
||||||
|
|
||||||
character_items:
|
|
||||||
enabled: true
|
|
||||||
for_each: party.characters
|
|
||||||
prompt_id: dnd.character_items
|
|
||||||
profile_id: production
|
|
||||||
output_path_pattern: artifacts/characters/{character_id}/items.md
|
|
||||||
inputs:
|
|
||||||
transcript:
|
|
||||||
source: narratio.transcript.final_trimmed
|
|
||||||
required: true
|
|
||||||
item_occurrences:
|
|
||||||
source: narratio.extraction.item_occurrences
|
|
||||||
required: true
|
|
||||||
member_vars:
|
|
||||||
character_name: character.name
|
|
||||||
```
|
|
||||||
|
|
||||||
Families support the ordinary result-affecting fields shared by concrete
|
|
||||||
Scriptorium artifacts: enablement, prompt and profile IDs, timeout,
|
|
||||||
render-debug policy, ordinary dependencies, inputs, and static variables.
|
|
||||||
`output_path_pattern` replaces concrete `output_path` at the family level.
|
|
||||||
|
|
||||||
`for_each` is not an expression language. This feature accepts only the exact
|
|
||||||
source `party.characters`. `member_vars` maps a Scriptorium variable name to
|
|
||||||
one of a closed set of canonical values:
|
|
||||||
|
|
||||||
- `character_id`;
|
|
||||||
- `player.name`;
|
|
||||||
- `character.name`;
|
|
||||||
- `character.class_summary`; and
|
|
||||||
- `character.alias_summary`.
|
|
||||||
|
|
||||||
Static `vars` and resolved member variables merge deterministically. Duplicate
|
|
||||||
variable names across the two maps are rejected rather than assigned implicit
|
|
||||||
precedence. Narratio's existing reserved sticky-session variable remains
|
|
||||||
application-owned and is applied after expansion under its current rules.
|
|
||||||
|
|
||||||
### Narrow substitution
|
|
||||||
|
|
||||||
`{character_id}` is the only family substitution supported in this feature.
|
|
||||||
It is required in `output_path_pattern` and may appear exactly where documented
|
|
||||||
for family-owned publish destinations. Unknown, repeated in an invalid
|
|
||||||
position, unresolved, or malformed substitutions are errors. Names, aliases,
|
|
||||||
classes, environment values, and arbitrary YAML paths cannot be interpolated
|
|
||||||
into configuration strings.
|
|
||||||
|
|
||||||
### Concrete expansion
|
|
||||||
|
|
||||||
For each family and party character, resolution creates one ordinary concrete
|
|
||||||
artifact key by joining the normalized family key, an underscore, and the
|
|
||||||
stable character ID:
|
|
||||||
|
|
||||||
```text
|
|
||||||
character_meta_arannis
|
|
||||||
character_items_arannis
|
|
||||||
```
|
|
||||||
|
|
||||||
Expansion must be sorted, deterministic, and completed before existing
|
|
||||||
configured-artifact defaults and validation. Every generated artifact then
|
|
||||||
uses the existing catalog, dependency planner, fingerprinting, manifest,
|
|
||||||
reconciliation, execution, materialization, and publish boundaries.
|
|
||||||
|
|
||||||
Validation rejects:
|
|
||||||
|
|
||||||
- invalid family keys or character IDs;
|
|
||||||
- generated keys that fail the existing configured-artifact grammar;
|
|
||||||
- collisions between generated artifacts, explicit concrete artifacts, or
|
|
||||||
family names;
|
|
||||||
- duplicate or escaping output paths;
|
|
||||||
- a family with no canonical party source;
|
|
||||||
- invalid member-variable selectors;
|
|
||||||
- a selected profile that leaves an executable family incomplete; and
|
|
||||||
- any expanded artifact that fails ordinary concrete-artifact validation.
|
|
||||||
|
|
||||||
An added party character creates missing concrete artifacts. Removing a
|
|
||||||
character removes those artifacts from effective configuration so existing
|
|
||||||
analyze reconciliation classifies their old records as removed. A change to a
|
|
||||||
member field changes only fingerprints that actually consume its resolved
|
|
||||||
value or the party document as an input.
|
|
||||||
|
|
||||||
### Dependencies between families
|
|
||||||
|
|
||||||
Ordinary `depends_on` continues to name shared concrete artifacts such as
|
|
||||||
`session_recap`. Add a typed `member_dependencies` list for corresponding
|
|
||||||
members of another family:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
character_items:
|
|
||||||
member_dependencies:
|
|
||||||
- character_meta
|
|
||||||
```
|
|
||||||
|
|
||||||
For `arannis`, this expands to a dependency on
|
|
||||||
`character_meta_arannis`. The referenced family must use the same party source
|
|
||||||
and generate the same character ID. Cycles and missing members are rejected by
|
|
||||||
the existing concrete dependency validation after expansion.
|
|
||||||
|
|
||||||
When a family input consumes the corresponding member output, provide a narrow
|
|
||||||
pre-expansion member-artifact source form owned by configuration resolution.
|
|
||||||
The syntax is:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
inputs:
|
|
||||||
prior_meta:
|
|
||||||
source: narratio.member_artifact.character_meta
|
|
||||||
required: true
|
|
||||||
```
|
|
||||||
|
|
||||||
For the `arannis` member, this resolves to
|
|
||||||
`narratio.artifact.character_meta_arannis` before ordinary runtime validation.
|
|
||||||
The referenced family must also appear in `member_dependencies`. The
|
|
||||||
`narratio.member_artifact.*` form must not survive into stage or adapter
|
|
||||||
configuration as a new runtime artifact kind.
|
|
||||||
|
|
||||||
### Selection and reporting
|
|
||||||
|
|
||||||
Extend artifact selection so an exact family key selects all of that family's
|
|
||||||
concrete members. An exact generated key selects only that member. Explicitly
|
|
||||||
selecting a disabled family follows the existing rule for explicitly selected
|
|
||||||
disabled concrete artifacts: all generated targets must still have valid
|
|
||||||
executable fields. Mixed family and concrete selections are normalized and
|
|
||||||
deduplicated after expansion.
|
|
||||||
|
|
||||||
Planning, run summaries, artifact listing, and effective-configuration output
|
|
||||||
must show both family origin and concrete artifact identity. Execution remains
|
|
||||||
entirely concrete; a family is not a dynamic pipeline stage or runtime loop.
|
|
||||||
|
|
||||||
### Family publish rules
|
|
||||||
|
|
||||||
Allow a family to declare one typed publish policy so operators need not repeat
|
|
||||||
one publish output per character:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
publish:
|
|
||||||
enabled: true
|
|
||||||
required: false
|
|
||||||
dest_pattern: artifacts/characters/{character_id}/meta.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Resolution expands this into ordinary concrete publish rules. When
|
|
||||||
`dest_pattern` is omitted, the generated artifact's output path is used where
|
|
||||||
the existing publish contract permits derivation. Conflicts with explicit
|
|
||||||
publish rules, duplicate destinations, unsafe paths, and generated sources
|
|
||||||
that are unavailable are rejected by the existing publish policy after
|
|
||||||
expansion. Family publish behavior must not introduce wildcard source matching
|
|
||||||
at runtime.
|
|
||||||
|
|
||||||
## Profile-Safe Resume And Invalidation
|
|
||||||
|
|
||||||
Named profiles are safe only if result-affecting model and configuration
|
|
||||||
changes cannot silently reuse incompatible successful stage results. Artifact
|
|
||||||
analysis already has a versioned semantic fingerprint that includes effective
|
|
||||||
prompt/profile identifiers, variables, dependencies, inputs, and output
|
|
||||||
identity. The broader pipeline requires the same principle at every applicable
|
|
||||||
stage boundary.
|
|
||||||
|
|
||||||
Audit each stage's resume contract and add a versioned semantic configuration
|
|
||||||
fingerprint wherever current evidence does not already cover all
|
|
||||||
Narratio-observable result-affecting settings. In particular, switching an
|
|
||||||
Audita model through a profile must not reuse polish output produced by the
|
|
||||||
testing model. Conversely, changing only an operational timeout, binary path,
|
|
||||||
workspace path, diagnostic location, or profile name must not invalidate
|
|
||||||
byte-equivalent semantic work unless that value genuinely affects the stage's
|
|
||||||
canonical result.
|
|
||||||
|
|
||||||
The selected profile name and complete effective-config digest are retained as
|
|
||||||
provenance. Resume decisions use stage-specific effective semantics rather
|
|
||||||
than the profile name as a blanket invalidator. This permits production and
|
|
||||||
testing profiles to share unaffected transcript work while correctly staling
|
|
||||||
changed stages and their fixed transitive dependents.
|
|
||||||
|
|
||||||
Private configuration, prompt, model, executable, and module contents loaded
|
|
||||||
inside an external tool remain outside Narratio's observable fingerprint
|
|
||||||
boundary unless an existing integration contract exposes their identity.
|
|
||||||
Changing such private inputs continues to require explicit force, and the
|
|
||||||
documentation must not claim otherwise.
|
|
||||||
|
|
||||||
## Configuration Inspection
|
|
||||||
|
|
||||||
Add a `config` command family that performs composition without running a
|
|
||||||
session:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
narratio config validate [--config <pipeline.yml>] [--profile <name>]
|
|
||||||
narratio config show [--config <pipeline.yml>] [--profile <name>]
|
|
||||||
narratio config sources [--config <pipeline.yml>] [--profile <name>]
|
|
||||||
narratio config diff <left-profile> <right-profile> [--config <pipeline.yml>]
|
|
||||||
```
|
|
||||||
|
|
||||||
Required behavior:
|
|
||||||
|
|
||||||
- `validate` composes imports and the selected profile, resolves campaign data
|
|
||||||
when required for party-driven families, expands artifacts and publish rules,
|
|
||||||
applies defaults, and runs strict validation without creating session or run
|
|
||||||
state.
|
|
||||||
- `show` emits deterministic normalized effective YAML, including generated
|
|
||||||
concrete artifacts, without raw credentials or environment values.
|
|
||||||
- `sources` reports root, import, profile, campaign, and party provenance and
|
|
||||||
identifies which source owns effective configuration paths.
|
|
||||||
- `diff` compares normalized effective configurations rather than raw files
|
|
||||||
and clearly identifies additions, removals, and value changes.
|
|
||||||
- output order is deterministic and suitable for review, but output formatting
|
|
||||||
is not a substitute for the effective configuration digest.
|
|
||||||
|
|
||||||
If family expansion requires a campaign, these commands accept the existing
|
|
||||||
campaign selection mechanisms or report clearly that pipeline-only validation
|
|
||||||
cannot complete the party-dependent portion. They must not silently choose an
|
|
||||||
unintended campaign.
|
|
||||||
|
|
||||||
## Required Application Changes
|
|
||||||
|
|
||||||
The implemented feature will require coordinated changes across existing
|
|
||||||
owners rather than a parallel configuration subsystem:
|
|
||||||
|
|
||||||
- `internal/config`: presence-aware YAML composition, import confinement,
|
|
||||||
profile overlays, provenance, effective digesting, party parsing and
|
|
||||||
validation, players projection, family expansion, defaults, and final strict
|
|
||||||
validation;
|
|
||||||
- `internal/app`: `--profile` plumbing through every configuration-consuming
|
|
||||||
command, `config` inspection commands, campaign-aware inspection, profile
|
|
||||||
reporting, and one shared loading path;
|
|
||||||
- `internal/artifactpolicy` and `internal/artifacts`: generated concrete source
|
|
||||||
registration, family-aware selection metadata, derived-player identity, and
|
|
||||||
reuse of the existing configured-artifact grammar and path policy;
|
|
||||||
- `internal/stage`: consumption only of expanded concrete artifacts, party and
|
|
||||||
derived-player preparation, and stage-specific semantic resume evidence;
|
|
||||||
- `internal/manifest`: bounded profile/effective-config provenance and any
|
|
||||||
versioned stage semantic fingerprints required for safe reuse;
|
|
||||||
- publish planning: expansion of family publish declarations into existing
|
|
||||||
concrete rules before ordinary validation and execution;
|
|
||||||
- Notarius composition: continue passing prepared `party` and `players`
|
|
||||||
sources, with canonical mode sourcing both from the party-owned contract;
|
|
||||||
and
|
|
||||||
- Scriptorium composition: receive only resolved concrete artifacts and
|
|
||||||
string/bool variables under the existing adapter contract.
|
|
||||||
|
|
||||||
There must be one production configuration-loading path. Remote session
|
|
||||||
loading, restore, status, planning, helper commands, and execution must not
|
|
||||||
independently reimplement import, profile, campaign, or family selection.
|
|
||||||
|
|
||||||
## Documentation And Examples
|
|
||||||
|
|
||||||
When behavior is implemented, update the canonical owners in the same change:
|
|
||||||
|
|
||||||
- [`docs/config.md`](../config.md): import, merge, profile, party selection,
|
|
||||||
family fields, defaults, and validation contracts;
|
|
||||||
- [`docs/cli.md`](../cli.md): `--profile` and `config` command syntax and output
|
|
||||||
behavior;
|
|
||||||
- [`docs/operations.md`](../operations.md): recommended production/testing
|
|
||||||
bundle layout, migration, inspection, and profile-switching workflow;
|
|
||||||
- [`docs/policy/architecture.md`](../policy/architecture.md): only the durable
|
|
||||||
composition, campaign-data ownership, and semantic-resume invariants;
|
|
||||||
- `docs/integrations/`: the canonical party and derived players YAML contracts;
|
|
||||||
- [`docs/internal/overview.md`](../internal/overview.md) and focused internal
|
|
||||||
configuration, manifest, analyze, prepare, and publish documents: implemented
|
|
||||||
ownership and mechanics; and
|
|
||||||
- `examples/`: a validated split configuration bundle, production/testing
|
|
||||||
overlays, canonical party roster with aliases and multiclass data, at least
|
|
||||||
two party-driven artifact families, and representative family publishing.
|
|
||||||
|
|
||||||
Do not copy complete example bundles into reference prose. Maintained examples
|
|
||||||
must be secret-free and loaded by the repository's example validation test.
|
|
||||||
|
|
||||||
## Testing Expectations
|
|
||||||
|
|
||||||
Testing should protect the public contracts and high-risk composition and
|
|
||||||
state-reuse behavior without coupling to private merge helpers. Required
|
|
||||||
confidence includes:
|
|
||||||
|
|
||||||
- monolithic configuration compatibility;
|
|
||||||
- recursive mapping composition and all duplicate/type/list conflict classes;
|
|
||||||
- import confinement, missing files, non-regular files, unsupported nesting,
|
|
||||||
source-aware diagnostics, deterministic ordering, and duplicate YAML keys;
|
|
||||||
- explicit zero-value profile overrides, list replacement, keyed-map additions,
|
|
||||||
unknown profiles, invalid defaults, and absence of profile stacking;
|
|
||||||
- canonical party schema validation, aliases, alias/name ambiguity, multiclass
|
|
||||||
summaries, stable IDs, malformed data, and legacy/canonical mode separation;
|
|
||||||
- deterministic players projection and prepared-input provenance;
|
|
||||||
- deterministic multi-family expansion, generated identities and paths,
|
|
||||||
variable binding, same-member dependencies, collisions, additions,
|
|
||||||
removals, and family selection;
|
|
||||||
- expansion into ordinary analyze planning, fingerprints, manifests, partial
|
|
||||||
selection, failure handling, and publish rules;
|
|
||||||
- model/profile changes staling only stages whose semantic configuration
|
|
||||||
changed while unaffected transcript work remains reusable;
|
|
||||||
- representative CLI validation, show, sources, diff, plan, and run behavior;
|
|
||||||
and
|
|
||||||
- maintained monolithic, imported, production, and testing examples loading
|
|
||||||
and validating offline.
|
|
||||||
|
|
||||||
Use package-level behavioral tests through the narrowest stable owner. A small
|
|
||||||
number of assembled workflow tests should prove that composed profiles and
|
|
||||||
party-driven families reach the existing pipeline correctly; higher-level
|
|
||||||
tests should not repeat every parser and validation case.
|
|
||||||
|
|
||||||
## Compatibility And Migration
|
|
||||||
|
|
||||||
- Existing monolithic pipeline files remain supported.
|
|
||||||
- Omitting `--profile` retains existing behavior for configurations without
|
|
||||||
profiles and selects the declared production default for composed bundles.
|
|
||||||
- Existing explicit concrete Scriptorium artifacts and publish rules remain
|
|
||||||
valid and may coexist with non-colliding families.
|
|
||||||
- Legacy opaque party plus players files remain temporarily usable only through
|
|
||||||
the isolated compatibility mode and cannot power party-driven families.
|
|
||||||
- Migration consists of converting the campaign party document to
|
|
||||||
`narratio.party.v1`, removing the separate players file, and then enabling
|
|
||||||
party-driven families.
|
|
||||||
- Configuration inspection must make the migration result reviewable before a
|
|
||||||
session run.
|
|
||||||
- Compatibility code and documentation must clearly identify the eventual
|
|
||||||
removal boundary; new features must target canonical party mode rather than
|
|
||||||
extend legacy semantics.
|
|
||||||
|
|
||||||
## Non-Goals And Deferred Work
|
|
||||||
|
|
||||||
This feature does not introduce:
|
|
||||||
|
|
||||||
- automatic loading of every file in `conf.d`;
|
|
||||||
- recursive imports, imports in profile overlays, or profile inheritance;
|
|
||||||
- multiple simultaneously active profiles;
|
|
||||||
- environment-selected profiles;
|
|
||||||
- generic YAML patch/delete operations;
|
|
||||||
- arbitrary string interpolation, expressions, scripting, or loops;
|
|
||||||
- artifact expansion from arbitrary files, Notarius outputs, or runtime data;
|
|
||||||
- configurable pipeline stages or a workflow DAG;
|
|
||||||
- runtime wildcard artifact or publish sources;
|
|
||||||
- automatic hashing of private external-tool configuration, prompts, modules,
|
|
||||||
models, or executables; or
|
|
||||||
- immediate removal of the isolated legacy party/player compatibility path.
|
|
||||||
|
|
||||||
Named reusable parameter sets beyond the canonical party roster may be
|
|
||||||
considered later if a second non-party use case demonstrates a need. Additional
|
|
||||||
party-member types, campaign-domain fields, or family iteration sources should
|
|
||||||
be added only through a versioned schema decision rather than an untyped
|
|
||||||
extension map.
|
|
||||||
|
|
||||||
## Target End State
|
|
||||||
|
|
||||||
The feature is complete when:
|
|
||||||
|
|
||||||
- operators can organize a pipeline as a root file plus explicit additive
|
|
||||||
imports with deterministic, source-aware conflict errors;
|
|
||||||
- one production-default or explicitly selected testing profile applies a
|
|
||||||
deliberate override without duplicating stable configuration;
|
|
||||||
- every command uses the same resolved profile and reports its identity;
|
|
||||||
- a campaign owns one strict, versioned party roster containing stable
|
|
||||||
character IDs, players, character names, optional alias lists, and one or
|
|
||||||
more classes;
|
|
||||||
- Narratio derives the existing players input from that roster and supplies the
|
|
||||||
canonical campaign context to Notarius;
|
|
||||||
- multiple Scriptorium artifact families expand once per campaign character
|
|
||||||
into ordinary concrete artifacts, dependencies, variables, output paths, and
|
|
||||||
optional publish rules;
|
|
||||||
- adding, changing, or removing a character produces deterministic and
|
|
||||||
manifest-correct artifact reconciliation;
|
|
||||||
- profile model changes cannot silently reuse semantically incompatible stage
|
|
||||||
outputs and do not unnecessarily invalidate unaffected transcript work;
|
|
||||||
- operators can validate, display, trace, and compare effective configuration
|
|
||||||
without executing a session;
|
|
||||||
- existing monolithic and concrete-artifact configurations remain compatible;
|
|
||||||
and
|
|
||||||
- current user, operator, integration, architecture, internal, and example
|
|
||||||
documentation accurately owns the implemented contracts.
|
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
# Release Procedure Upgrade
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
Implemented. This document records the accepted release-process scope,
|
|
||||||
maintainer policy, and delivered end state.
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Give Narratio one reproducible, guarded minor/patch release procedure that
|
|
||||||
validates a source candidate before publication, creates the existing binary
|
|
||||||
release inputs consistently, and makes the exact upstream tag push the clear
|
|
||||||
completion boundary.
|
|
||||||
|
|
||||||
The procedure should borrow WeatherReporter's binary-publication model and
|
|
||||||
Notarius's centralized source-candidate validation and immutable-tag guards,
|
|
||||||
while retaining Narratio's own repository-wide checks and six supported build
|
|
||||||
targets.
|
|
||||||
|
|
||||||
## Responsibility Boundary
|
|
||||||
|
|
||||||
A Narratio release is complete when the selected tag has been successfully
|
|
||||||
pushed to its exact tag ref in the upstream Git repository. The release command
|
|
||||||
must then return successfully without polling Woodpecker, waiting for a
|
|
||||||
pipeline, querying a Gitea release, downloading assets, or otherwise making its
|
|
||||||
result depend on CI availability.
|
|
||||||
|
|
||||||
The tag event may asynchronously cause Woodpecker to validate the tagged tree,
|
|
||||||
build binaries and checksums, and create a Gitea release. That work is outside
|
|
||||||
the synchronous release procedure. CI failure or delay does not change whether
|
|
||||||
the Git tag was successfully published, and release tooling must never wait
|
|
||||||
indefinitely for infrastructure outside the upstream Git push.
|
|
||||||
|
|
||||||
An operator may inspect CI or published assets later. Such inspection is an
|
|
||||||
optional asynchronous follow-up, not a release completion condition and not a
|
|
||||||
step that an automated coding agent must perform after pushing the tag.
|
|
||||||
|
|
||||||
## Release Model
|
|
||||||
|
|
||||||
- Releases use unused stable semantic-version tags in the form
|
|
||||||
`vMAJOR.MINOR.PATCH`; prereleases are outside this feature.
|
|
||||||
- Narratio is past `v1.0.0`, so a minor release adds backward-compatible
|
|
||||||
functionality, a patch release contains backward-compatible fixes, and an
|
|
||||||
intentional breaking change to a documented public contract requires a new
|
|
||||||
major version.
|
|
||||||
- Release tags remain lightweight, matching Narratio's established tag
|
|
||||||
history, and are bound explicitly to a guarded commit on `main`.
|
|
||||||
- Every new release has a nonempty checked-in note at
|
|
||||||
`docs/releases/<tag>.md` in the tagged tree.
|
|
||||||
- Published tags are immutable. They are never moved, reused, overwritten, or
|
|
||||||
deleted, even when asynchronous CI fails to create a complete binary
|
|
||||||
release.
|
|
||||||
- The Gitea release, when asynchronous publication succeeds, remains the
|
|
||||||
canonical source for downloadable binaries and checksums. The Git tag and
|
|
||||||
checked-in note remain valid source-release history independently of CI.
|
|
||||||
- Existing historical tags and notes are not retroactively rewritten or
|
|
||||||
backfilled to satisfy the new procedure.
|
|
||||||
|
|
||||||
## Canonical Documentation
|
|
||||||
|
|
||||||
Add `docs/release.md` as the canonical maintainer procedure. Update
|
|
||||||
`docs/development.md` to route release preparation, tagging, and publication to
|
|
||||||
it, and update the documentation policy so it assigns ownership as follows:
|
|
||||||
|
|
||||||
- `docs/release.md` owns version selection, candidate preparation, validation,
|
|
||||||
tag guards, tag publication, the responsibility boundary, and failure
|
|
||||||
recovery;
|
|
||||||
- `docs/releases/<tag>.md` owns the immutable historical summary for one
|
|
||||||
release;
|
|
||||||
- `docs/releases/README.md` owns the release-note index and points to the Gitea
|
|
||||||
release collection for downloadable assets; and
|
|
||||||
- current CLI, configuration, operation, integration, and internal contracts
|
|
||||||
remain in their existing canonical documents rather than being restated in
|
|
||||||
release notes.
|
|
||||||
|
|
||||||
The maintainer procedure must be written as a complete safe workflow suitable
|
|
||||||
for either a human maintainer or a coding agent. Commands must be explicit,
|
|
||||||
non-interactive, and scoped to the selected version and tag ref.
|
|
||||||
|
|
||||||
## Release Notes
|
|
||||||
|
|
||||||
Require each future note to use this minimum structure:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Narratio vMAJOR.MINOR.PATCH
|
|
||||||
|
|
||||||
This release ...
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
|
|
||||||
## Upgrade
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
```
|
|
||||||
|
|
||||||
The compatibility section must identify changes to documented CLI,
|
|
||||||
configuration, durable artifact, integration, or operating contracts. The
|
|
||||||
upgrade section must state required operator actions or explicitly say that no
|
|
||||||
special action is required. When the Notarius consumer contract changes, the
|
|
||||||
note should identify the supported/tested contract version and any required
|
|
||||||
Notarius upgrade.
|
|
||||||
|
|
||||||
Release notes summarize a historical candidate and link to current canonical
|
|
||||||
documentation for exact behavior. They must not substitute for updating that
|
|
||||||
documentation, include credentials or private infrastructure details, or make
|
|
||||||
claims that have not been validated against the candidate.
|
|
||||||
|
|
||||||
## Shared Release Scripts
|
|
||||||
|
|
||||||
### Source-candidate checker
|
|
||||||
|
|
||||||
Add the POSIX-shell checker:
|
|
||||||
|
|
||||||
```text
|
|
||||||
scripts/check-release-candidate.sh vMAJOR.MINOR.PATCH
|
|
||||||
```
|
|
||||||
|
|
||||||
It must be deterministic, non-interactive, safe to run repeatedly, and usable
|
|
||||||
both by maintainers before tagging and by tag CI in a detached checkout. It
|
|
||||||
must not create or push Git tags, modify tracked source, contact CI, or require
|
|
||||||
live WhisperX, LLM, Notarius, Scriptorium, object-storage, or notification
|
|
||||||
services.
|
|
||||||
|
|
||||||
The checker must fail clearly unless all of the following hold:
|
|
||||||
|
|
||||||
- the argument is a stable semantic version;
|
|
||||||
- `go.mod` declares Narratio's expected module path;
|
|
||||||
- the matching release note exists, has the exact Narratio/version heading,
|
|
||||||
and contains the required sections;
|
|
||||||
- Go workspace use is disabled for candidate commands;
|
|
||||||
- tracked `go.work`/`go.work.sum`, vendoring, and `go.mod` replacement
|
|
||||||
directives are absent;
|
|
||||||
- uncached repository tests, race tests, vet, build, documentation checks, and
|
|
||||||
maintained-example validation succeed;
|
|
||||||
- `go mod tidy -diff` reports no module-file change;
|
|
||||||
- every tracked Go source file is `gofmt` clean;
|
|
||||||
- working-tree and cached whitespace checks succeed; and
|
|
||||||
- all supported release binaries cross-compile with the selected version
|
|
||||||
embedded, with the host-platform binary reporting exactly
|
|
||||||
`narratio <tag>` through `narratio version`.
|
|
||||||
|
|
||||||
The checker must preserve the distinction already documented by Narratio:
|
|
||||||
cross-compilation is compilation evidence, not native macOS or Windows runtime
|
|
||||||
evidence.
|
|
||||||
|
|
||||||
### Asset builder
|
|
||||||
|
|
||||||
Add `scripts/build-release-assets.sh`, used by both the candidate checker and
|
|
||||||
release CI. It must build exactly these CGO-disabled targets:
|
|
||||||
|
|
||||||
- Linux `amd64` and `arm64`;
|
|
||||||
- macOS `amd64` and `arm64`; and
|
|
||||||
- Windows `amd64` and `arm64`.
|
|
||||||
|
|
||||||
Asset names must remain deterministic and include the Narratio version,
|
|
||||||
operating system, architecture, and `.exe` suffix for Windows. Build output
|
|
||||||
must use a caller-supplied, narrowly validated staging directory and must not
|
|
||||||
perform broad or ambiguous deletion. Official assets use `-trimpath` and the
|
|
||||||
established release version linker override.
|
|
||||||
|
|
||||||
### Tag-publication command
|
|
||||||
|
|
||||||
Add the narrowly scoped release command:
|
|
||||||
|
|
||||||
```text
|
|
||||||
scripts/release.sh vMAJOR.MINOR.PATCH
|
|
||||||
```
|
|
||||||
|
|
||||||
The command owns local validation, publication guards, lightweight tag
|
|
||||||
creation, local tag verification, and pushing only the selected tag ref. It
|
|
||||||
must not commit candidate changes or push `main`; the candidate must already be
|
|
||||||
committed and present on upstream `main`.
|
|
||||||
|
|
||||||
Before creating a tag, the command must:
|
|
||||||
|
|
||||||
- run the shared candidate checker;
|
|
||||||
- require the current branch to be `main`;
|
|
||||||
- require a clean worktree and index;
|
|
||||||
- fetch upstream `main` and tags;
|
|
||||||
- record the exact candidate commit and require local `HEAD` to equal
|
|
||||||
`origin/main`;
|
|
||||||
- require the version-matched release note from that commit; and
|
|
||||||
- reject an existing local or upstream tag of the selected name.
|
|
||||||
|
|
||||||
It must create an explicitly lightweight tag against the recorded commit,
|
|
||||||
verify that the local tag resolves directly to that commit, and push only:
|
|
||||||
|
|
||||||
```text
|
|
||||||
refs/tags/<version>:refs/tags/<version>
|
|
||||||
```
|
|
||||||
|
|
||||||
It must never use `git push --tags`. If the tag push succeeds, the command must
|
|
||||||
report the version and commit and exit successfully immediately. In
|
|
||||||
particular, it must not invoke a CI client, poll a build status, wait for a
|
|
||||||
Gitea release, or verify release assets.
|
|
||||||
|
|
||||||
If a failure occurs before the upstream tag is published, no release has
|
|
||||||
completed. An unpublished local tag may be removed only after the maintainer
|
|
||||||
has inspected the failure. The script should not silently delete it. A
|
|
||||||
successful upstream push is final even if later asynchronous work fails.
|
|
||||||
|
|
||||||
## Woodpecker Release Workflow
|
|
||||||
|
|
||||||
Retain tag-triggered asynchronous release automation, but make it consume the
|
|
||||||
same checked-in contracts as local preparation:
|
|
||||||
|
|
||||||
- validate the tag syntax and matching release note before asset work;
|
|
||||||
- call the shared candidate checker rather than maintaining an independent
|
|
||||||
drifting validation list;
|
|
||||||
- use the shared asset builder for the six official binaries;
|
|
||||||
- pin the Go builder image to `golang:1.25.5`, an explicit patch version
|
|
||||||
compatible with the current `go.mod` language version;
|
|
||||||
- pin the release plugin to the reviewed explicit version
|
|
||||||
`woodpeckerci/plugin-release:0.3.1` rather than a floating image;
|
|
||||||
- set the Gitea release title explicitly to `Narratio <tag>`;
|
|
||||||
- use `docs/releases/<tag>.md` from the tagged tree as the release body;
|
|
||||||
- generate a flattened `SHA256SUMS` file for all official binaries;
|
|
||||||
- keep prerelease publication disabled and overwrite disabled; and
|
|
||||||
- keep validation, asset building, and publication dependency-ordered so a
|
|
||||||
failed CI validation cannot publish assets.
|
|
||||||
|
|
||||||
The workflow must not require a callback to, acknowledgement from, or status
|
|
||||||
update in the release command. The two sides have intentionally separate
|
|
||||||
responsibilities:
|
|
||||||
|
|
||||||
```text
|
|
||||||
local release command asynchronous infrastructure
|
|
||||||
--------------------- ---------------------------
|
|
||||||
validate candidate
|
|
||||||
guard main and exact commit
|
|
||||||
create lightweight tag
|
|
||||||
push exact upstream tag ────────► validate tagged tree
|
|
||||||
return success immediately build assets and checksums
|
|
||||||
create Gitea release
|
|
||||||
```
|
|
||||||
|
|
||||||
## Failure And Correction Policy
|
|
||||||
|
|
||||||
Failures before tag publication are corrected on `main`, followed by complete
|
|
||||||
candidate revalidation and fresh guards.
|
|
||||||
|
|
||||||
After a tag has been pushed, neither CI failure nor a defect permits changing
|
|
||||||
that tag. Correct the cause on `main`, choose a new patch version, write a new
|
|
||||||
release note, and repeat the complete release procedure. Do not move or delete
|
|
||||||
the published tag, manually overwrite generated assets, or make the release
|
|
||||||
command wait for repaired infrastructure.
|
|
||||||
|
|
||||||
A maintainer may later inspect the remote tag, Gitea note, assets, checksums,
|
|
||||||
and host binary. Documentation may provide these as optional asynchronous
|
|
||||||
inspection commands, clearly outside the release responsibility boundary. No
|
|
||||||
automated release instruction may require them after a successful tag push.
|
|
||||||
|
|
||||||
## Security And Operational Constraints
|
|
||||||
|
|
||||||
- Local scripts never read or print the Gitea release token; the token remains
|
|
||||||
a Woodpecker-owned secret supplied only to the release plugin.
|
|
||||||
- Candidate validation remains offline with respect to paid APIs, mutable
|
|
||||||
application services, and private user data.
|
|
||||||
- Temporary build output is confined to a validated temporary or staging
|
|
||||||
directory and cleaned safely.
|
|
||||||
- Scripts avoid environment dumps and never record credentials in release
|
|
||||||
notes, logs, repository files, or command output.
|
|
||||||
- The release command does not edit tracked files, create commits, or push
|
|
||||||
branches. Apart from fetching refs for its guards, its only release-state
|
|
||||||
mutations are creating the selected local tag and pushing that exact tag.
|
|
||||||
|
|
||||||
## Target End State
|
|
||||||
|
|
||||||
When this feature is complete:
|
|
||||||
|
|
||||||
1. A maintainer or coding agent prepares and commits a standardized release
|
|
||||||
note plus any final canonical-documentation updates.
|
|
||||||
2. The candidate is pushed to `main` through the ordinary development
|
|
||||||
workflow.
|
|
||||||
3. One shared checker proves the exact tagged source is clean, self-contained,
|
|
||||||
formatted, tested, race-tested, statically checked, documented, module
|
|
||||||
tidy, cross-buildable, and version-correct.
|
|
||||||
4. One guarded release command refuses ambiguous or unpublished candidates,
|
|
||||||
creates the established lightweight tag, and pushes only that tag.
|
|
||||||
5. The command completes successfully at the upstream tag push and performs no
|
|
||||||
CI or Gitea status wait.
|
|
||||||
6. Woodpecker independently reuses the shared checks and asset builder and may
|
|
||||||
publish six versioned binaries, release notes, and SHA-256 checksums.
|
|
||||||
7. Published tags remain immutable, and failures after tag publication are
|
|
||||||
corrected only through a new version.
|
|
||||||
8. The release procedure is documented in one canonical location, linked from
|
|
||||||
contributor guidance, enforced by scripts and CI, and clear about the
|
|
||||||
boundary between guaranteed Git publication and best-effort asynchronous
|
|
||||||
binary publication.
|
|
||||||
|
|
||||||
## Non-Goals
|
|
||||||
|
|
||||||
This feature does not:
|
|
||||||
|
|
||||||
- repair, replace, monitor, or improve Woodpecker reliability;
|
|
||||||
- wait for or synchronously verify CI or Gitea release completion;
|
|
||||||
- introduce prereleases, signed tags, containers, package-manager publication,
|
|
||||||
installers, auto-updaters, or native macOS/Windows test runners;
|
|
||||||
- publish `main` automatically or create release-note content automatically;
|
|
||||||
- run live external adapters or paid model calls during release validation; or
|
|
||||||
- rewrite historical tags or manufacture retrospective release notes.
|
|
||||||
Reference in New Issue
Block a user