docs(arch): formal contract specs — config + manifest + workflow API (#367) #475
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!475
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/367-contracts"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Doc 2 of the architecture-doc arc (release-toolkit#367):
docs/architecture/contracts/. Basev2/next. Doc-only.Turns ADR-0008's central finding — the adopter API boundary is the workflow YAML + the on-disk file formats, not the scripts — from prose into testable contracts.
config.schema.jsonmanifest.schema.jsonworkflow-api.mdREADME.mdGrounding
config.shv0.1 schema (release_type enum required; version_files basename-constrained to {VERSION, package.json}; tag_format {version}-only; publish_mode/section_format/pre_v1_breaking_to_minor/default_branch/release_author/post_bump_hooks with documented defaults).schema:1+last_released_{sha,version,tag,at}, grounded in the jq write atreusable-release.yml:425-433; SHA pattern-constrained to 40-hex.uses:@<ref>input/output surface (inputs: runs_on req + bump_override/dry_run/publish_mode/config_path/manifest_path; outputs: mode/next_version/next_tag/cut_version/cut_tag/rolling_pr_number), plus thesecrets: inherit+RELEASE_TOOLKIT_TOKENplatform-quirk contract that ADR-0008 §6 R5 says must NOT be cleaned up in the port.Both JSON schemas validated well-formed.
Why "contract" not "documentation"
Enforceable: (1) cross-substrate equivalence (shell + Go assert against the same schemas — makes byte-identical a checkable property), (2) additive-only evolution (the operational meaning of ADR-0008's adopter-transparent axis), (3) schema-first for new features (the prerelease-cut capability extends the contract first).
Cross-references
tag_formatnote + README's known-findings section carry the branch-strategy.md §3 prerelease-write-side finding forward (schema-first framing).Refs #367.
Doc 2 of the architecture-doc arc. Turns ADR-0008's workflow-YAML-as-API-boundary finding from prose into testable contracts: - config.schema.json — JSON Schema for release-toolkit.yml (grounded in config.sh v0.1 schema: release_type enum required, version_files basename-constrained, tag_format/{version}-only, publish_mode/section_format/post_bump_hooks, defaults) - manifest.schema.json — JSON Schema for .release-toolkit-manifest.json (schema:1 + last_released_{sha,version,tag,at}, grounded in reusable-release.yml:425-433) - workflow-api.md — typed spec of the reusable-workflow uses:@<ref> input/output surface + the secrets:inherit / RELEASE_TOOLKIT_TOKEN platform-quirk contract - README.md — framing: contracts (not docs) enable cross-substrate equivalence, additive-only evolution, schema-first for new features Both schemas validated well-formed. Contracts bind SHAPE; behavior (resolution layers, decision logic) is Arc42 §6 Runtime View, explicitly non-scope here. Refs #367APPROVED — PR#475 @
ae5efcc(docs/architecture/contracts/, doc 2 of 6)Formal contract specs (config + manifest JSON schemas, workflow-API typed table, README). For a contract doc the review question isn't "does it read well" — it's does every documented field match what the code actually reads/writes, and does it cover the whole surface. I verified both directions on-source; the fidelity is high. One should-consider, no must-fix.
Completeness — verified in both directions (the property that matters most for a contract)
changelog/tag_format/release_type/versionvia_config_get_field, anddefault_branch/publish_mode/release_author/section_format.{tag_prefix,separator}/post_bump_hooks/version_files/pre_v1_breaking_to_minorvia directyqgetters inconfig.sh. (The first two I checked missed theyq-getter fields — they're real, just read through a different accessor.)yq -r '.<key>'+_config_get_fieldread acrossscripts/— the set maps 1:1 onto the schema's properties. Nothing the code reads is undocumented. This is the direction that protects the byte-equivalence target: a v2 substrate can't silently drop a field the schema doesn't list, because the schema lists them all.config.schema.json — defaults + constraints match source
release_typeconfig_get_release_type"No default — required field" (config.sh:81)changelogCHANGELOG.md_config_get_field … changelog CHANGELOG.md(config.sh:102)tag_formatv{version}config_get_tag_formatdefaultv{version}version_filesitemsVERSION|package.jsonCONFIG_SUPPORTED_VERSION_FILE_BASENAMES=(VERSION package.json)(config.sh:41)section_format.tag_prefix/separatorv/" - "yq … // "v"/// " - "(config.sh:272,…)default_branch""yq '.default_branch // ""'(config.sh:148)post_bump_hooks[]yq '.post_bump_hooks[]?'(config.sh:334)manifest.schema.json — write-site + shape exact
{schema, last_released_sha, last_released_version, last_released_tag, last_released_at}; the live repo manifest has exactly those five, no extras — soadditionalProperties: falseis faithful to what the toolkit produces.schemaconst: 1— the create-pathjq -n '{schema: 1, …}'atreusable-release.yml:429emits it, and the live manifest carriesschema: 1. Cite:425-433is correct (that's the create branch; the sibling:418-421PATCH branch updates the fourlast_released_*and preservesschema).last_released_sha^[0-9a-f]{40}$+ therelease-decide.shhistory-existence guard — matches.workflow-api.md — inputs / outputs / secrets all faithful
runs_on(required),bump_override(''),dry_run('false'),publish_mode('draft'),config_path('release-toolkit.yml'),manifest_path('.release-toolkit-manifest.json') — every name/type/required/default matchesreusable-release.yml:44-71.mode/next_version/next_tag/cut_version/cut_tag/rolling_pr_number) exist and match.secrets: inherit/ "don't declare asecrets:block or Forgejo parse-fails" note + the||-hazard bash fallback (#41/#47) are accurately described; cite:77-89is correct. Good that this is captured as a platform constraint the Go port must not "clean up" — it's exactly the kind of quirk a rewrite would naively drop.Should-consider — layered-field
defaultkeyword is applied on inconsistent layerspublish_modeandversionare resolution-layered fields whose config getters return""/absent (config_get_publish_mode=yq '.publish_mode // ""';config_get_schema_version=_config_get_field … version ""), with the effective default supplied downstream (the'draft'workflow-input default +resolve_publish_mode; the "absence = current schema" caller check). But the schema stamps"default": "draft"and"default": 1on them — the terminal/effective default — whereasdefault_branch, an identically-shaped// ""getter, is documented with"default": ""— the config-layer default. So the three layered fields answer "what's the default" from two different vantage points.Neither is wrong (from an adopter's "if I omit it" view,
draft/1are what you effectively get), but for a contract that a byte-equivalence harness may read defaults from, the mixed convention is a latent trip-hazard. Suggested tightening: pick one convention — e.g.default= the config-layer value (""for all three getters), and name the terminal/effective default indescription.versionalready reconciles this in prose ("absence is treated as the current schema");publish_modewould benefit from the same explicit note rather than the baredefault: "draft". Low-stakes, purely a consistency-of-record tightening.Gate note
CI empty/0 on head — by design (no
branches:[main]match), same as #474; my gate was on-source reproduction, not green-CI. Disjoint paths from #474 (contracts/*vsbranch-strategy.md), so no cross-PR composition concern even with both open on v2/next.Clean to merge as contract-of-record. The specs are an accurate, complete freeze of the current surface — a sound fixed target for both the shell validation and the v2 equivalence harness.
— Surveyor
APPROVED (re-stamp on new head) — PR#475 @
75e3dbcRe-approval after HEAD moved
ae5efcc → 75e3dbc(the default-convention sweep from my 3725 should-consider). Verified the delta — it is exactly the convention fix, correctly extended to the full class.Delta check —
git diff ae5efcc 75e3dbc— convention-sweep onlyconfig.schema.json(+2/-4). No other file moved.publish_mode—"default": "draft"dropped; the prose now states the config-layer getter returns empty (likedefault_branch) and namesresolve_publish_modeas the downstream applier of the terminal'draft'. Matches my note.version—"default": 1dropped; prose names the config-layer-empty / terminal-1 split. This is the disclose-and-extend: I namedpublish_mode; you correctly identifiedversionas the second member of the same "resolved-downstream// ""getter" class and swept both. The fix touches 2 sites, disclosed — so what-changed == what-I-verified.default_branch— unchanged; retains"default": "". Correct: it already exemplified the config-layer convention, so it's the reference the other two now conform to.Convention now consistent across all three layered fields:
defaultkeyword = config-layer value; terminal/effective defaults named indescription. No behavioral surface — doc-only refinement of my own note. The bidirectional fidelity verification behind 3725 (complete both directions, all field names/defaults/manifest/workflow-API confirmed on-source) carries forward unchanged. Re-approved on75e3dbc.— Surveyor