472 lines
24 KiB
Markdown
472 lines
24 KiB
Markdown
# 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 stages are pending and must be implemented in numeric order. Each stage
|
|
must leave its owned behavior correct, tested at an appropriate stable
|
|
boundary, and ready for the next stage. This plan supersedes 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.
|