Compare commits

..

4 Commits

Author SHA1 Message Date
e33a6d6704 feat(standards): serve service version-wiring rule + bundle wiki pages in MCP
Versioning standard now states services must read their VERSION (e.g. a build arg) rather than hardcoding it; the compliance prompt repeats it. build_bundle also bundles src/wiki/** as wiki_-prefixed modules (e.g. docs://modules/wiki_development_releasing), so the release runbook is reachable from the MCP. Bundle grows to 22 modules.
2026-10-15 18:50:03 +05:30
5971f26c08 feat(standards): MR-based tag strategy + repository inventory (registry://repos)
Versioning standard now documents the MR flow: bump pyproject.toml/VERSION and promote CHANGELOG in the MR (referencing the version/tag, not a commit id), merge/squash, then tag the resulting commit. Adds the agent-assisted tagging procedure (wait for the go-ahead, find the version-bump commit, tag only it, ask the user to push) to both the reference page and the releasing wiki runbook. Adds the repo inventory (src/repos.json -> mcp/repos.json), the registry://repos resource + list_repos tool, the 15_repository_inventory lib page, and two MCP prompts: compliance (remediate repos) and release (agent tagging).
2026-10-15 14:39:21 +05:30
df03f41771 docs(standards): rebuild lib + wiki HTML for versioning/changelog and drone pages 2026-10-15 14:21:50 +05:30
8cc9631fe7 feat(standards): add versioning + changelog standard, release templates, and Drone version guard
Add lib page 11 (SemVer contract, pre-1.0 rule, tag==version invariant, Keep a Changelog), the releasing wiki runbook, and canonical release templates (CHANGELOG.md, VERSION). Vendor them into the MCP bundle as templates/release + release.json and expose registry://release with list/get tools. Add a changelog component to the app template. Enforce the tag<->version contract in both Drone shapes: Type A check-version and the backfill loop verify pyproject version; Type B adds a VERSION guard step.
2026-10-15 01:24:25 +05:30
63 changed files with 6293 additions and 16 deletions

141
build.py
View File

@@ -99,13 +99,26 @@ def _read_title(path: Path) -> str:
return _module_name(path.stem).replace("_", " ").title()
def _wiki_module_name(rel: Path) -> str:
"""Map a wiki page path to a namespaced module name.
``04_development/02_releasing.md`` -> ``wiki_development_releasing``; the
``wiki_`` prefix keeps wiki pages from colliding with lib modules (both have
an ``index``).
"""
parts = [NUM_PREFIX.sub("", part) or part for part in rel.with_suffix("").parts]
return "wiki_" + "_".join(parts)
def build_bundle(folder: Path, dry_run: bool = False) -> None:
"""Regenerate ``<name>/mcp`` from ``folder/src/lib`` markdown.
"""Regenerate ``<name>/mcp`` from ``folder/src/lib`` and ``folder/src/wiki``.
The bundle mirrors the lib site (in the same way doc-forge's MCP output
mirrors its lib documentation). Writes ``index.json``, ``nav.json`` and
``modules/*.json`` in the doc-forge MCP bundle shape. Hand-authored extras
(e.g. ``libraries.json``) are left untouched.
(e.g. ``libraries.json``) are left untouched. Wiki pages (``src/wiki/**``)
are also bundled, under a ``wiki_`` namespaced module name, so the runbooks
are reachable from the MCP.
Args:
folder (Path): The hub folder owning a ``src/lib``.
@@ -149,6 +162,17 @@ def build_bundle(folder: Path, dry_run: bool = False) -> None:
"title": _read_title(path),
})
wiki = folder / "src" / "wiki"
if wiki.is_dir():
for path in sorted(wiki.rglob("*.md")):
module = _wiki_module_name(path.relative_to(wiki))
write_module(module, path.read_text(encoding="utf-8"))
nav.append({
"module": module,
"resource": f"docs://modules/{module}",
"title": _read_title(path),
})
(out_dir / "nav.json").write_text(
json.dumps(nav, indent=2, ensure_ascii=False), encoding="utf-8"
)
@@ -239,6 +263,8 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [
"description": "OpenAPI contract skeleton."},
{"kind": "pyproject", "path": "pyproject.toml", "placeholder": "",
"description": "Packaging + dependency pins."},
{"kind": "changelog", "path": "CHANGELOG.md", "placeholder": "",
"description": "Keep a Changelog file with an [Unreleased] section (versioning standard)."},
{"kind": "dockerfile", "path": "Dockerfile", "placeholder": "",
"description": "API + worker image with private-index wiring."},
{"kind": "dockerignore", "path": ".dockerignore", "placeholder": "",
@@ -412,6 +438,115 @@ def build_drone_templates(folder: Path, dry_run: bool = False) -> None:
f"({len(manifest['components'])} components)")
# ── Release standard bundle ───────────────────────────────────────────────────
# The release artefacts shipped in ``src/templates/release``. One canonical
# changelog (Keep a Changelog + SemVer) and the service version file. See
# standards "11_versioning_and_changelog".
_RELEASE_COMPONENTS: list[dict[str, str]] = [
{"kind": "changelog", "path": "CHANGELOG.md", "placeholder": "",
"description": "Keep a Changelog skeleton: one [Unreleased] section + compare links."},
{"kind": "version-file", "path": "VERSION", "placeholder": "",
"description": "Single-line SemVer source of truth for services and sites."},
]
def build_release_manifest(template_root: Path) -> dict[str, object]:
"""Describe every artefact in the vendored release standard.
Args:
template_root (Path): The vendored ``templates/release`` directory.
Returns:
dict[str, object]: Manifest with the component entries and the
tag == version invariant.
"""
components: list[dict[str, str]] = []
for entry in _RELEASE_COMPONENTS:
rel = entry["path"]
if not (template_root / rel).exists():
raise SystemExit(f"[ERR] release template missing: {rel}")
components.append({
"kind": entry["kind"],
"path": rel,
"placeholder": entry["placeholder"],
"description": entry["description"],
})
return {
"project": "standards",
"template": "templates/release",
"invariant": "git tag == version file (enforced by the Drone guard)",
"components": components,
}
def build_release_templates(folder: Path, dry_run: bool = False) -> None:
"""Vendor ``folder/src/templates/release`` into ``<name>/mcp/templates/release``.
Ships the canonical ``CHANGELOG.md`` and ``VERSION`` in the MCP bundle so an
agent can drop them into a repo, and emits ``release.json`` describing them.
Args:
folder (Path): The hub folder owning ``src/templates/release``.
dry_run (bool): Print the plan without executing.
"""
name = folder.name
src = folder / "src" / "templates" / "release"
if not src.is_dir():
return
out_dir = folder / "mcp" / "templates" / "release"
if dry_run:
print(f"[REL] {name} {src.relative_to(ROOT)}/** -> {out_dir.relative_to(ROOT)}/**")
return
if out_dir.exists():
shutil.rmtree(out_dir)
out_dir.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(
src, out_dir,
ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".git"),
)
manifest = build_release_manifest(out_dir)
(folder / "mcp" / "release.json").write_text(
json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8"
)
print(f"[REL] {name} release templates vendored to {out_dir.relative_to(ROOT)}/ "
f"({len(manifest['components'])} components)")
# ── Repository inventory bundle ───────────────────────────────────────────────
def build_repos(folder: Path, dry_run: bool = False) -> None:
"""Publish ``folder/src/repos.json`` as ``<name>/mcp/repos.json``.
The repository inventory (name, type, version source, Drone shape) is a
single hand-authored JSON file, surfaced to agents through the
``registry://repos`` resource and the ``list_repos`` tool. See standards
"15_repository_inventory".
Args:
folder (Path): The hub folder owning ``src/repos.json``.
dry_run (bool): Print the plan without executing.
"""
name = folder.name
src = folder / "src" / "repos.json"
if not src.is_file():
return
out = folder / "mcp" / "repos.json"
if dry_run:
print(f"[REP] {name} {src.relative_to(ROOT)} -> {out.relative_to(ROOT)}")
return
data = json.loads(src.read_text(encoding="utf-8"))
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(
json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8"
)
print(f"[REP] {name} repo inventory written to {out.relative_to(ROOT)} "
f"({len(data.get('repos', []))} repos)")
# ── CLI ──────────────────────────────────────────────────────────────────────
@@ -432,6 +567,8 @@ def main() -> None:
build_bundle(folder, dry_run=args.dry_run)
build_templates(folder, dry_run=args.dry_run)
build_drone_templates(folder, dry_run=args.dry_run)
build_release_templates(folder, dry_run=args.dry_run)
build_repos(folder, dry_run=args.dry_run)
build_html(folder, dry_run=args.dry_run)

View File

@@ -11,6 +11,8 @@ Resources:
registry://libraries (the structured catalog)
registry://components (the template component index)
registry://drone (the CI template index)
registry://release (the release template index)
registry://repos (the repository inventory)
Tools:
ping (from the base server)
@@ -20,12 +22,17 @@ Tools:
get_app_template (the whole app skeleton as a path -> content map)
list_drone_templates (the two CI shapes: PyPI package / Docker deployment)
get_drone_template (kind -> exact .drone.yml template)
list_release_templates (the changelog + version-file artefacts)
get_release_template (kind -> exact release template file)
list_repos (the covered repositories: type, path, version source)
Prompts:
intake (the questions to gather before building)
build (the two axes -> deterministic build prompt)
start_app (combined entry: intake, then build)
trial (self-contained trial build into a scratch dir)
compliance (remediate repos against the CI + versioning standard)
release (agent-assisted tagging after the release MR is merged)
"""
from __future__ import annotations
@@ -91,6 +98,46 @@ def _build_text(app_name: str, resources: str, pipelines: str, auth: str) -> str
)
_COMPLIANCE_TEXT = (
"# Compliance — align repositories with the CI + versioning standard\n\n"
"Read `docs://modules/drone_pipelines` and `docs://modules/versioning_and_changelog`,\n"
"then enumerate the working set with `registry://repos` (or `list_repos()`).\n\n"
"For each repository, bring it to the standard for its `type`:\n\n"
"- **package** — `version` in `pyproject.toml`; root `CHANGELOG.md`; Drone file\n"
" from `get_drone_template(\"drone-pypi\")` (`python-lib.yaml`, tag-triggered).\n"
"- **service / site** — root `VERSION`; root `CHANGELOG.md`; Drone file from\n"
" `get_drone_template(\"drone-service\")` (`service-docker.yaml`; tag-triggered\n"
" service variant, push-triggered site variant). The service must read its\n"
" version from `VERSION` (e.g. a build arg) — never hardcode it.\n\n"
"Steps:\n"
"1. Inspect each repo (`git -C <path> tag`, version file, `CHANGELOG.md`,\n"
" `.drone.yml`) and list the gaps — never assume.\n"
"2. Add missing files from `get_release_template(\"changelog\"/\"version-file\")`.\n"
"3. Replace `.drone.yml` with the guarded template for the repo's type.\n"
"4. Align the version file with the intended tag.\n"
"5. Commit per repo (one focused commit); **do not push or tag** without the\n"
" user's explicit go-ahead.\n"
)
_RELEASE_TEXT = (
"# Release — tag the merged commit\n\n"
"The version bump and changelog promotion live **in the release merge\n"
"request**; the tag is created on the commit that results from merging it.\n"
"See `docs://modules/versioning_and_changelog`.\n\n"
"Do this only after the user confirms the MR is merged/squashed and tells you\n"
"to proceed — never tag before the merge.\n\n"
"1. Wait for that explicit go-ahead.\n"
"2. Find the commit that introduced the version bump `x.y.z`:\n"
" - package: `git log -S 'version = \"x.y.z\"' -n 1 --format='%H %s' -- pyproject.toml`\n"
" - service / site: `git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`\n"
"3. Verify that commit's version file equals `x.y.z` (the Drone guard enforces\n"
" `tag == version`).\n"
"4. Tag **only** that commit, lightweight and bare: `git tag x.y.z <sha>`.\n"
"5. **Do not push.** Ask the user to run `git push origin x.y.z`, then Drone\n"
" builds the release.\n"
)
class StandardsMCPServer(MCPServer):
"""MCP server for the `standards` bundle with discovery/scaffolding tools.
@@ -136,6 +183,39 @@ class StandardsMCPServer(MCPServer):
return {"error": "not_found", "path": str(path), "components": []}
return json.loads(path.read_bytes())
def _release(self) -> dict[str, Any]:
"""Load the release (versioning + changelog) template manifest."""
path = Path(self.mcp_root) / "release.json"
if not path.exists():
return {"error": "not_found", "path": str(path), "components": []}
return json.loads(path.read_bytes())
def _repos(self) -> dict[str, Any]:
"""Load the repository inventory."""
path = Path(self.mcp_root) / "repos.json"
if not path.exists():
return {"error": "not_found", "path": str(path), "repos": []}
return json.loads(path.read_bytes())
def _find_release_component(self, kind: str) -> dict[str, Any] | None:
for entry in self._release().get("components", []):
if entry.get("kind") == kind:
return entry
return None
def _read_release_component(self, entry: dict[str, Any]) -> dict[str, Any]:
"""Read a vendored release standard file."""
rel = entry["path"]
path = Path(self.mcp_root) / "templates" / "release" / rel
if not path.exists():
return {"error": "not_found", "path": str(path)}
return {
"kind": entry["kind"],
"path": rel,
"placeholder": entry.get("placeholder"),
"content": path.read_text(encoding="utf-8"),
}
def _find_drone_component(self, kind: str) -> dict[str, Any] | None:
for entry in self._drone().get("components", []):
if entry.get("kind") == kind:
@@ -228,6 +308,20 @@ class StandardsMCPServer(MCPServer):
def drone() -> dict[str, Any]:
return self._drone()
@self.app.resource("registry://release")
def release_templates() -> dict[str, Any]:
return self._release()
@self.app.resource("registry://repos")
def repos() -> dict[str, Any]:
"""Return the repository inventory.
Use this to enumerate the repositories covered by the CI and
versioning standards, each with its type, path, version source, and
Drone shape. Paths are relative to the aetos/ workspace root.
"""
return self._repos()
@self.app.resource("docs://spec")
def spec() -> dict[str, Any]:
"""Return the pinned auth-server OpenAPI contract.
@@ -380,6 +474,66 @@ class StandardsMCPServer(MCPServer):
}
return self._read_drone_component(entry)
@self.app.tool()
def list_release_templates() -> list[dict[str, Any]]:
"""List the release/versioning template artefacts.
These are the files every repo carries for releases: a Keep a
Changelog ``CHANGELOG.md`` and, for services, a root ``VERSION``
file. See standards "11_versioning_and_changelog".
Returns:
list[dict[str, Any]]:
Entries with ``kind``, ``path``, ``placeholder`` and
``description``. Fetch one with :func:`get_release_template`.
"""
return self._release().get("components", [])
@self.app.tool()
def get_release_template(kind: str) -> dict[str, Any]:
"""Return the exact release standard file for a component kind.
Use this to drop the canonical changelog / version file into a repo
instead of inventing one. See :func:`list_release_templates` for
valid kinds ("changelog", "version-file").
Args:
kind (str): Component kind, e.g. "changelog" or "version-file".
Returns:
dict[str, Any]: ``{kind, path, placeholder, content}`` or an
error entry when the kind is unknown.
"""
entry = self._find_release_component(kind)
if entry is None:
return {
"error": "unknown_kind",
"kind": kind,
"available": [c["kind"] for c in self._release().get("components", [])],
}
return self._read_release_component(entry)
@self.app.tool()
def list_repos(type: str | None = None) -> list[dict[str, Any]]:
"""List the repositories covered by the CI and versioning standards.
Each entry has: ``name``, ``type`` ("package" | "service" | "site"),
``path`` (relative to the aetos/ workspace root), ``version_source``
("pyproject.toml" | "VERSION"), ``drone`` (the template file),
``trigger`` ("tag" | "push"), ``docs`` kinds, and ``mcp``.
Args:
type (str | None): Optional filter, e.g. "package" or "site".
Returns:
list[dict[str, Any]]: Matching repository entries.
"""
repos = self._repos().get("repos", [])
if type:
needle = type.strip().lower()
return [r for r in repos if str(r.get("type", "")).lower() == needle]
return repos
@self.app.prompt()
def intake() -> str:
"""Return the questions to gather before building an app.
@@ -472,3 +626,28 @@ class StandardsMCPServer(MCPServer):
"back so the standard can be amended.\n\n"
f"Delete `{target}` once you are satisfied.\n"
)
@self.app.prompt()
def compliance() -> str:
"""Return the procedure to align repositories with the standard.
Use this to audit and remediate the repos listed in
`registry://repos` against the Drone and versioning standards.
Returns:
str: The compliance/remediation prompt.
"""
return _COMPLIANCE_TEXT
@self.app.prompt()
def release() -> str:
"""Return the agent-assisted tagging procedure.
Use after the user confirms the release MR is merged/squashed: find
the commit that bumped the version file, tag only that commit, and
ask the user to push the tag.
Returns:
str: The release/tagging prompt.
"""
return _RELEASE_TEXT

View File

@@ -549,6 +549,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -540,6 +540,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -531,6 +531,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -558,6 +558,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -567,6 +567,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -558,6 +558,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -531,6 +531,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -540,6 +540,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -14,7 +14,7 @@
<link rel="prev" href="../08_human_in_loop/">
<link rel="next" href="../12_genericity_matrix/">
<link rel="next" href="../10_drone_pipelines/">
<link rel="icon" href="../assets/images/favicon.png">
@@ -558,6 +558,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -0,0 +1,957 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="The rules and library registry of the standardized application blueprint.">
<link rel="prev" href="../09_infra_adaptors/">
<link rel="next" href="../11_versioning_and_changelog/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
<title>Drone Pipelines - Aetos Standards — Reference</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#drone-pipelines" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="Aetos Standards — Reference" class="md-header__button md-logo" aria-label="Aetos Standards — Reference" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Aetos Standards — Reference
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Drone Pipelines
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan" aria-hidden="true" type="radio" name="__palette" id="__palette_0">
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="Aetos Standards — Reference" class="md-nav__button md-logo" aria-label="Aetos Standards — Reference" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Aetos Standards — Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../01_build_recipe/" class="md-nav__link">
<span class="md-ellipsis">
Build Recipe
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<span class="md-ellipsis">
Layering Rules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<span class="md-ellipsis">
Composition Root
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<span class="md-ellipsis">
Spec-First Routing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<span class="md-ellipsis">
CRUD Template
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<span class="md-ellipsis">
Pipeline Scaffold
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<span class="md-ellipsis">
Worker & Progress
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<span class="md-ellipsis">
Human-in-the-Loop
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<span class="md-ellipsis">
Infra Adapters
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Drone Pipelines
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#type-a-pypi-package-python-libyaml" class="md-nav__link">
<span class="md-ellipsis">
Type A — PyPI package (python-lib.yaml)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#type-b-docker-deployment-service-dockeryaml" class="md-nav__link">
<span class="md-ellipsis">
Type B — Docker deployment (service-docker.yaml)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#what-not-to-do" class="md-nav__link">
<span class="md-ellipsis">
What not to do
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<span class="md-ellipsis">
Genericity Matrix
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<span class="md-ellipsis">
Library Registry
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#type-a-pypi-package-python-libyaml" class="md-nav__link">
<span class="md-ellipsis">
Type A — PyPI package (python-lib.yaml)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#type-b-docker-deployment-service-dockeryaml" class="md-nav__link">
<span class="md-ellipsis">
Type B — Docker deployment (service-docker.yaml)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#what-not-to-do" class="md-nav__link">
<span class="md-ellipsis">
What not to do
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="drone-pipelines">Drone Pipelines</h1>
<h2 id="summary">Summary</h2>
<p>Every Aetos repository ships a Drone CI definition at <code>.drone.yml</code>. There is
exactly <strong>one standard per repository</strong>, and there are <strong>two types of file</strong>,
chosen by what the repository produces:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>File</th>
<th>Produces</th>
<th>Repos</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>A — PyPI package</strong></td>
<td><code>templates/drone/python-lib.yaml</code></td>
<td>A wheel + sdist on the private index</td>
<td><code>dagpipe</code>, <code>doc-forge</code>, <code>hexa</code>, <code>mail-intake</code>, <code>mongo-ops</code>, <code>omniread</code>, <code>openapi-first</code>, <code>py-jwt</code></td>
</tr>
<tr>
<td><strong>B — Docker deployment</strong></td>
<td><code>templates/drone/service-docker.yaml</code></td>
<td>An image pushed to the registry and run as a container</td>
<td><code>auth-server</code>, <code>docs</code>, <code>mcp</code></td>
</tr>
</tbody>
</table>
<p>Both templates run on <code>platform: linux / arm64</code> with the workspace at
<code>/drone/src</code>. Copy the matching template to <code>&lt;repo&gt;/.drone.yml</code>; the Type A file
needs no edits, the Type B file needs its placeholders filled in (below).</p>
<blockquote>
<p>Conventions: <code>.drone.yml</code> (not <code>.yaml</code>), one file per repo, secrets injected
with <code>from_secret</code> — never commit credentials.</p>
</blockquote>
<hr />
<h2 id="type-a-pypi-package-python-libyaml">Type A — PyPI package (<code>python-lib.yaml</code>)</h2>
<p>For internal libraries. The file contains <strong>two pipelines</strong>:</p>
<ol>
<li><strong><code>build-and-publish-pypi</code></strong> — triggered on <code>tag</code>:</li>
<li><code>check-version</code> — <strong>guard</strong>: fail unless <code>$DRONE_TAG</code> equals the version in
<code>pyproject.toml</code>; then read <code>name</code> / <code>version</code> and query
<code>$PIP_REPO_URL/simple/&lt;package&gt;/</code>; if that version already exists, exit
<code>78</code> and skip the whole build (see <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a>).</li>
<li><code>quality-gate</code> — install the package with its <code>[dev]</code> extras from the
private index, then <code>black --check</code>, <code>ruff check</code>, <code>mypy</code>, <code>pydoclint</code>,
<code>pytest</code>.</li>
<li><code>build-package</code> — <code>python -m build</code> into <code>dist/</code>.</li>
<li><code>upload-to-private-pypi</code> — <code>twine upload</code> to <code>$PIP_REPO_URL</code>.</li>
<li><strong><code>backfill-pypi-from-tags</code></strong> — triggered on <code>custom</code>:</li>
<li><code>fetch-tags</code> — <code>git fetch --tags --force</code>.</li>
<li><code>build-and-upload-missing</code> — walk every tag in version order, skip the ones
already on the index, <code>git checkout</code> the rest, verify <code>tag == pyproject
version</code> at that commit, build, and upload. This is the disaster-recovery /
replay path.</li>
</ol>
<p>Secrets: <code>PIP_REPO_URL</code>, <code>PIP_USERNAME</code>, <code>PIP_PASSWORD</code>.</p>
<ul>
<li><strong>Ship as-is.</strong> Package name and version come from <code>pyproject.toml</code>, so the
same file is reused verbatim across every library.</li>
</ul>
<hr />
<h2 id="type-b-docker-deployment-service-dockeryaml">Type B — Docker deployment (<code>service-docker.yaml</code>)</h2>
<p>For services and sites that ship a container image. The file contains <strong>two
pipelines</strong>, each mirroring a repo family:</p>
<ol>
<li><strong><code>build-and-deploy-service</code></strong> — triggered on <code>tag</code> (a long-running
API/worker service, e.g. <code>auth-server</code>):</li>
<li><code>fetch-tags</code> — resolve the latest tag into <code>LATEST_TAG.txt</code>; fail if there
are no tags.</li>
<li><code>check-version</code> — <strong>guard</strong>: fail unless <code>$DRONE_TAG</code> equals the repo-root
<code>VERSION</code> file (see <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a>).</li>
<li><code>check-remote-image</code> — if <code>aetos/&lt;app&gt;:&lt;tag&gt;</code> already exists locally, exit
<code>78</code> and skip the build.</li>
<li><code>quality-gate</code> — install from <code>requirements.txt</code> and run
<code>black</code> / <code>ruff</code> / <code>mypy</code> / <code>pytest</code>.</li>
<li><code>build-image</code> — <code>docker build</code> with the private-index build args, tagged
<code>aetos/&lt;app&gt;:&lt;tag&gt;</code> and <code>:latest</code>.</li>
<li><code>push-image</code> — login, re-tag with <code>$REGISTRY_HOST</code>, push both tags.</li>
<li><code>stop-old</code> — <code>docker rm -f &lt;app&gt;</code>.</li>
<li><code>run-container</code> — <code>docker run -d --name &lt;app&gt; -p &lt;host&gt;:&lt;container&gt;
--restart always</code> with the service's <code>-e</code> environment.</li>
<li><strong><code>build-and-deploy-site</code></strong> — triggered on <code>push</code> (a static/HTTP site, e.g.
<code>docs</code>, <code>mcp</code>): <code>build-image</code> → <code>push-image</code> → <code>stop-old</code> → <code>run-container</code>,
with no tag resolution, no quality gate, and no version guard (it ships
<code>:latest</code> only).</li>
</ol>
<p>Secrets: <code>PIP_REPO_URL</code>, <code>PIP_USERNAME</code>, <code>PIP_PASSWORD</code> (service build args),
<code>REGISTRY_HOST</code>, <code>REGISTRY_USER</code>, <code>REGISTRY_PASS</code>, plus the service runtime env
(for example <code>MONGO_*</code>, <code>JWT_SECRET</code>).</p>
<p>Placeholders to fill in per repo:</p>
<table>
<thead>
<tr>
<th>Placeholder</th>
<th>Meaning</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&lt;app&gt;</code></td>
<td>service image / container name</td>
<td><code>auth-server</code></td>
</tr>
<tr>
<td><code>aetos/&lt;app&gt;</code></td>
<td>image repository on the registry</td>
<td><code>aetos/auth-server</code></td>
</tr>
<tr>
<td><code>&lt;host-port&gt;</code> / <code>&lt;container-port&gt;</code></td>
<td>published : in-image port</td>
<td><code>9003:8000</code></td>
</tr>
<tr>
<td><code>&lt;site&gt;</code></td>
<td>site image / container name</td>
<td><code>docs</code></td>
</tr>
<tr>
<td><code>&lt;ENV_VAR&gt;</code></td>
<td>runtime environment variable</td>
<td><code>MONGO_HOST</code></td>
</tr>
</tbody>
</table>
<hr />
<h2 id="what-not-to-do">What not to do</h2>
<ul>
<li>Do not hand-roll a third shape. A repo is either Type A or Type B, never both.</li>
<li>Do not inline secrets; always use <code>from_secret</code>.</li>
<li>Do not drop the <code>check-version</code> / <code>check-remote-image</code> skip — it guards the
index and the running container against duplicate publishes.</li>
<li>Do not drop the tag↔version guard. A package needs its <code>pyproject.toml</code>
version to equal the tag; a service needs a root <code>VERSION</code> file that equals
the tag. See <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a>.</li>
<li>Do not rename the pipelines; tooling and runbooks key off
<code>build-and-publish-pypi</code>, <code>build-and-deploy-service</code>, and
<code>build-and-deploy-site</code>.</li>
</ul>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../01_build_recipe/">01 – Build Recipe</a> · <a href="../09_infra_adaptors/">09 – Infra Adapters</a> · <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a> · <a href="../13_library_registry/">13 – Library Registry</a></li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.sections", "navigation.expand", "navigation.top", "navigation.instant", "navigation.tracking", "navigation.indexes", "content.code.copy", "content.code.annotate", "content.tabs.link", "content.action.edit", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.f55a23d4.min.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
<link rel="prev" href="../09_infra_adaptors/">
<link rel="prev" href="../15_repository_inventory/">
<link rel="next" href="../14_auth_integration/">
@@ -442,6 +442,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>

View File

@@ -442,6 +442,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -444,6 +444,72 @@
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">

View File

@@ -0,0 +1,983 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="The rules and library registry of the standardized application blueprint.">
<link rel="prev" href="../11_versioning_and_changelog/">
<link rel="next" href="../12_genericity_matrix/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
<title>Repository Inventory - Aetos Standards — Reference</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#repository-inventory" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="Aetos Standards — Reference" class="md-header__button md-logo" aria-label="Aetos Standards — Reference" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Aetos Standards — Reference
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Repository Inventory
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan" aria-hidden="true" type="radio" name="__palette" id="__palette_0">
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="Aetos Standards — Reference" class="md-nav__button md-logo" aria-label="Aetos Standards — Reference" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Aetos Standards — Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../01_build_recipe/" class="md-nav__link">
<span class="md-ellipsis">
Build Recipe
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<span class="md-ellipsis">
Layering Rules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<span class="md-ellipsis">
Composition Root
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<span class="md-ellipsis">
Spec-First Routing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<span class="md-ellipsis">
CRUD Template
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<span class="md-ellipsis">
Pipeline Scaffold
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<span class="md-ellipsis">
Worker & Progress
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<span class="md-ellipsis">
Human-in-the-Loop
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<span class="md-ellipsis">
Infra Adapters
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Repository Inventory
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#packages-type-a" class="md-nav__link">
<span class="md-ellipsis">
Packages (Type A)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#services-sites-type-b" class="md-nav__link">
<span class="md-ellipsis">
Services &amp; sites (Type B)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#required-files-per-repo" class="md-nav__link">
<span class="md-ellipsis">
Required files per repo
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#reading-this-at-runtime" class="md-nav__link">
<span class="md-ellipsis">
Reading this at runtime
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<span class="md-ellipsis">
Genericity Matrix
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<span class="md-ellipsis">
Library Registry
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#packages-type-a" class="md-nav__link">
<span class="md-ellipsis">
Packages (Type A)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#services-sites-type-b" class="md-nav__link">
<span class="md-ellipsis">
Services &amp; sites (Type B)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#required-files-per-repo" class="md-nav__link">
<span class="md-ellipsis">
Required files per repo
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#reading-this-at-runtime" class="md-nav__link">
<span class="md-ellipsis">
Reading this at runtime
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="repository-inventory">Repository Inventory</h1>
<h2 id="summary">Summary</h2>
<p>The canonical list of Aetos repositories covered by the CI and versioning
standards, with the artifact each one produces, its version source, and its
Drone shape. Every entry maps to the contract in
<a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a> and the two CI
files in <a href="../10_drone_pipelines/">10 – Drone Pipelines</a>.</p>
<p>The same data is published machine-readably as the <code>registry://repos</code> MCP
resource (and the <code>list_repos</code> tool), sourced from <code>standards/src/repos.json</code>.
Paths are relative to the <code>aetos/</code> workspace root.</p>
<hr />
<h2 id="packages-type-a">Packages (Type A)</h2>
<p>A wheel + sdist published to the private index. Version source is
<code>pyproject.toml</code>; Drone file is <code>python-lib.yaml</code> (tag-triggered).</p>
<table>
<thead>
<tr>
<th>Repo</th>
<th>Path</th>
<th>Docs</th>
<th>MCP server</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>dagpipe</code></td>
<td><code>dagpipe</code></td>
<td>lib, wiki</td>
<td><code>dagpipe:8006</code></td>
</tr>
<tr>
<td><code>doc-forge</code></td>
<td><code>doc-forge</code></td>
<td>lib, wiki</td>
<td><code>docforge:8001</code></td>
</tr>
<tr>
<td><code>hexa</code></td>
<td><code>hexa</code></td>
<td>lib, wiki</td>
<td><code>hexa:8008</code></td>
</tr>
<tr>
<td><code>mail-intake</code></td>
<td><code>mail-intake</code></td>
<td>lib, wiki</td>
<td><code>mail_intake:8002</code></td>
</tr>
<tr>
<td><code>mongo-ops</code></td>
<td><code>mongo-ops</code></td>
<td>lib, wiki</td>
<td><code>mongo_ops:8007</code></td>
</tr>
<tr>
<td><code>omniread</code></td>
<td><code>omniread</code></td>
<td>lib, wiki</td>
<td><code>omniread:8003</code></td>
</tr>
<tr>
<td><code>openapi-first</code></td>
<td><code>openapi-first</code></td>
<td>lib, wiki</td>
<td><code>openapi_first:8004</code></td>
</tr>
<tr>
<td><code>py-jwt</code></td>
<td><code>py-jwt</code></td>
<td>lib, wiki</td>
<td><code>jwtlib:8005</code></td>
</tr>
</tbody>
</table>
<hr />
<h2 id="services-sites-type-b">Services &amp; sites (Type B)</h2>
<p>A container image pushed to the registry and run on the Docker host. Version
source is the <code>VERSION</code> file; Drone file is <code>service-docker.yaml</code>.</p>
<table>
<thead>
<tr>
<th>Repo</th>
<th>Type</th>
<th>Path</th>
<th>Trigger</th>
<th>Docs</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>auth-server</code></td>
<td>service</td>
<td><code>auth-server</code></td>
<td>tag</td>
<td>api, lib, wiki</td>
</tr>
<tr>
<td><code>docs</code></td>
<td>site</td>
<td><code>docs</code></td>
<td>push</td>
<td>—</td>
</tr>
<tr>
<td><code>mcp</code></td>
<td>site</td>
<td><code>mcp</code></td>
<td>push</td>
<td>—</td>
</tr>
</tbody>
</table>
<p>The <strong>service</strong> variant is tag-triggered and enforces <code>tag == VERSION</code>. The
<strong>site</strong> variant is push-triggered, ships <code>:latest</code>, and has no tag guard.</p>
<hr />
<h2 id="required-files-per-repo">Required files per repo</h2>
<table>
<thead>
<tr>
<th>Type</th>
<th>Version source</th>
<th>Changelog</th>
<th>Drone</th>
</tr>
</thead>
<tbody>
<tr>
<td>package</td>
<td><code>pyproject.toml</code></td>
<td><code>CHANGELOG.md</code></td>
<td><code>python-lib.yaml</code></td>
</tr>
<tr>
<td>service / site</td>
<td><code>VERSION</code></td>
<td><code>CHANGELOG.md</code></td>
<td><code>service-docker.yaml</code></td>
</tr>
</tbody>
</table>
<p>Every covered repository must be able to satisfy <code>tag == version</code>:
the version file (never a commit id) is bumped in the release MR, the MR is
merged, and the tag is created on the merged commit.</p>
<hr />
<h2 id="reading-this-at-runtime">Reading this at runtime</h2>
<ul>
<li>Resource: <code>registry://repos</code></li>
<li>Tool: <code>list_repos()</code></li>
<li>Humans: this page (mirror of <code>standards/src/repos.json</code>).</li>
</ul>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../10_drone_pipelines/">10 – Drone Pipelines</a> · <a href="../11_versioning_and_changelog/">11 – Versioning &amp; Changelog</a> · <a href="../13_library_registry/">13 – Library Registry</a></li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.sections", "navigation.expand", "navigation.top", "navigation.instant", "navigation.tracking", "navigation.indexes", "content.code.copy", "content.code.annotate", "content.tabs.link", "content.action.edit", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.f55a23d4.min.js"></script>
</body>
</html>

View File

@@ -435,6 +435,72 @@
<li class="md-nav__item">
<a href="/10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/12_genericity_matrix/" class="md-nav__link">

View File

@@ -452,6 +452,72 @@
<li class="md-nav__item">
<a href="10_drone_pipelines/" class="md-nav__link">
<span class="md-ellipsis">
Drone Pipelines
</span>
</a>
</li>
<li class="md-nav__item">
<a href="11_versioning_and_changelog/" class="md-nav__link">
<span class="md-ellipsis">
Versioning & Changelog
</span>
</a>
</li>
<li class="md-nav__item">
<a href="15_repository_inventory/" class="md-nav__link">
<span class="md-ellipsis">
Repository Inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="12_genericity_matrix/" class="md-nav__link">
@@ -561,6 +627,11 @@ patterns, and the internal library catalog that agents consult while coding.</p>
<li><strong>Worker &amp; Progress</strong> — queued work, progress reporting, and visibility.</li>
<li><strong>Human-in-the-Loop</strong> — guardrails and approvals inside automations.</li>
<li><strong>Infra Adapters</strong> — how infrastructure connects without leaking into domain.</li>
<li><strong>Drone Pipelines</strong> — the two CI shapes: a PyPI package or a Docker deployment.</li>
<li><strong>Versioning &amp; Changelog</strong> — SemVer, the tag↔version contract, and the release
changelog.</li>
<li><strong>Repository Inventory</strong> — every covered repo, its type, version source, and
Drone shape.</li>
<li><strong>Genericity Matrix</strong> — what is library-backed, template-provided, or authored.</li>
<li><strong>Auth Integration</strong> — consuming the shared auth-server service (spec-driven,
no auth code).</li>

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -186,6 +186,12 @@
"placeholder": "",
"description": "Packaging + dependency pins."
},
{
"kind": "changelog",
"path": "CHANGELOG.md",
"placeholder": "",
"description": "Keep a Changelog file with an [Unreleased] section (versioning standard)."
},
{
"kind": "dockerfile",
"path": "Dockerfile",

View File

@@ -1,6 +1,6 @@
{
"project": "standards",
"type": "standards",
"modules_count": 14,
"modules_count": 22,
"source": "hand-authored"
}

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
"module": "index",
"content": {
"path": "index",
"docstring": "# Standards Reference\n\nThe reference half of the Standards description: the enforceable rules,\npatterns, and the internal library catalog that agents consult while coding.\n\n- **Build Recipe** — the ordered, agentic checklist for a new app.\n- **Layering Rules** — the strict request → domain → repository layering and\n the rules that enforce it.\n- **Composition Root** — a single composition point wiring the whole app.\n- **Spec-First Routing** — the OpenAPI contract as the routing source of truth.\n- **CRUD Template** — the standardized generic resource endpoint scaffold.\n- **Pipeline Scaffold** — the deterministic pipeline skeleton for multi-stage\n jobs.\n- **Worker & Progress** — queued work, progress reporting, and visibility.\n- **Human-in-the-Loop** — guardrails and approvals inside automations.\n- **Infra Adapters** — how infrastructure connects without leaking into domain.\n- **Drone Pipelines** — the two CI shapes: a PyPI package or a Docker deployment.\n- **Genericity Matrix** — what is library-backed, template-provided, or authored.\n- **Auth Integration** — consuming the shared auth-server service (spec-driven,\n no auth code).\n- **Library Registry** — the catalog of internal libraries behind all of it.\n\nRead the **Wiki** for the philosophy and rationale; these pages are the\nreference that mirrors the `standards` MCP bundle.\n",
"docstring": "# Standards Reference\n\nThe reference half of the Standards description: the enforceable rules,\npatterns, and the internal library catalog that agents consult while coding.\n\n- **Build Recipe** — the ordered, agentic checklist for a new app.\n- **Layering Rules** — the strict request → domain → repository layering and\n the rules that enforce it.\n- **Composition Root** — a single composition point wiring the whole app.\n- **Spec-First Routing** — the OpenAPI contract as the routing source of truth.\n- **CRUD Template** — the standardized generic resource endpoint scaffold.\n- **Pipeline Scaffold** — the deterministic pipeline skeleton for multi-stage\n jobs.\n- **Worker & Progress** — queued work, progress reporting, and visibility.\n- **Human-in-the-Loop** — guardrails and approvals inside automations.\n- **Infra Adapters** — how infrastructure connects without leaking into domain.\n- **Drone Pipelines** — the two CI shapes: a PyPI package or a Docker deployment.\n- **Versioning & Changelog** — SemVer, the tag↔version contract, and the release\n changelog.\n- **Repository Inventory** — every covered repo, its type, version source, and\n Drone shape.\n- **Genericity Matrix** — what is library-backed, template-provided, or authored.\n- **Auth Integration** — consuming the shared auth-server service (spec-driven,\n no auth code).\n- **Library Registry** — the catalog of internal libraries behind all of it.\n\nRead the **Wiki** for the philosophy and rationale; these pages are the\nreference that mirrors the `standards` MCP bundle.\n",
"objects": {}
}
}

View File

@@ -0,0 +1,8 @@
{
"module": "repository_inventory",
"content": {
"path": "repository_inventory",
"docstring": "# Repository Inventory\n\n## Summary\n\nThe canonical list of Aetos repositories covered by the CI and versioning\nstandards, with the artifact each one produces, its version source, and its\nDrone shape. Every entry maps to the contract in\n[11 – Versioning & Changelog](11_versioning_and_changelog.md) and the two CI\nfiles in [10 – Drone Pipelines](10_drone_pipelines.md).\n\nThe same data is published machine-readably as the `registry://repos` MCP\nresource (and the `list_repos` tool), sourced from `standards/src/repos.json`.\nPaths are relative to the `aetos/` workspace root.\n\n---\n\n## Packages (Type A)\n\nA wheel + sdist published to the private index. Version source is\n`pyproject.toml`; Drone file is `python-lib.yaml` (tag-triggered).\n\n| Repo | Path | Docs | MCP server |\n|------|------|------|------------|\n| `dagpipe` | `dagpipe` | lib, wiki | `dagpipe:8006` |\n| `doc-forge` | `doc-forge` | lib, wiki | `docforge:8001` |\n| `hexa` | `hexa` | lib, wiki | `hexa:8008` |\n| `mail-intake` | `mail-intake` | lib, wiki | `mail_intake:8002` |\n| `mongo-ops` | `mongo-ops` | lib, wiki | `mongo_ops:8007` |\n| `omniread` | `omniread` | lib, wiki | `omniread:8003` |\n| `openapi-first` | `openapi-first` | lib, wiki | `openapi_first:8004` |\n| `py-jwt` | `py-jwt` | lib, wiki | `jwtlib:8005` |\n\n---\n\n## Services & sites (Type B)\n\nA container image pushed to the registry and run on the Docker host. Version\nsource is the `VERSION` file; Drone file is `service-docker.yaml`.\n\n| Repo | Type | Path | Trigger | Docs |\n|------|------|------|---------|------|\n| `auth-server` | service | `auth-server` | tag | api, lib, wiki |\n| `docs` | site | `docs` | push | — |\n| `mcp` | site | `mcp` | push | — |\n\nThe **service** variant is tag-triggered and enforces `tag == VERSION`. The\n**site** variant is push-triggered, ships `:latest`, and has no tag guard.\n\n---\n\n## Required files per repo\n\n| Type | Version source | Changelog | Drone |\n|------|----------------|-----------|-------|\n| package | `pyproject.toml` | `CHANGELOG.md` | `python-lib.yaml` |\n| service / site | `VERSION` | `CHANGELOG.md` | `service-docker.yaml` |\n\nEvery covered repository must be able to satisfy `tag == version`:\nthe version file (never a commit id) is bumped in the release MR, the MR is\nmerged, and the tag is created on the merged commit.\n\n---\n\n## Reading this at runtime\n\n- Resource: `registry://repos`\n- Tool: `list_repos()`\n- Humans: this page (mirror of `standards/src/repos.json`).\n\n---\n\n## Related\n\n- [10 – Drone Pipelines](10_drone_pipelines.md) · [11 – Versioning & Changelog](11_versioning_and_changelog.md) · [13 – Library Registry](13_library_registry.md)\n",
"objects": {}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
{
"module": "wiki_development_releasing",
"content": {
"path": "wiki_development_releasing",
"docstring": "# Releasing & Versioning\n\nHow to cut a release of any Aetos repository. The rule lives in the reference\npage [Versioning & Changelog](../lib/11_versioning_and_changelog/); this is the\nrunbook you follow.\n\nEvery repository is one of two Drone shapes — a **PyPI package** or a **Docker\ndeployment** ([Drone Pipelines](../lib/10_drone_pipelines/)). Both release the\nsame way: the version bump and changelog promotion go **in the merge request**,\nand the tag is created on the merged commit.\n\n---\n\n## 🧾 Before you start\n\n- You have an open **merge request** holding the change, and `main` is green\n (the Drone quality gate passed on the last run).\n- You know whether this is a **breaking**, **feature**, or **fix** release.\n- The version file exists:\n - package → `version` in `pyproject.toml`\n - service / site → `VERSION` at the repo root\n\n---\n\n## 🚦 Pick the next version\n\nThe version file and the tag must be identical. For `0.y.z`, `0.y` is the major\nline (see the reference page); once at `1.0.0`, normal SemVer applies.\n\n| Change | `0.y.z` → | `1.0.0+` → |\n|--------|-----------|------------|\n| Breaking | `0.(y+1).0` | `(x+1).0.0` |\n| Feature | `0.y.(z+1)` | `x.(y+1).0` |\n| Fix | `0.y.(z+1)` | `x.y.(z+1)` |\n\n---\n\n## 🌿 Release in the MR\n\nThe version is known while the change is still a merge request, so the bump\nrides along with it. Do both edits **in the MR**:\n\n- **Promote the changelog** in `CHANGELOG.md`: move the `[Unreleased]` content\n under a new `## [x.y.z] - YYYY-MM-DD` heading, leave a fresh empty\n `[Unreleased]` on top, and add the compare links — naming the **version/tag**,\n never a commit id:\n\n ```markdown\n [Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.z...HEAD\n [x.y.z]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.(z-1)...x.y.z\n ```\n\n- **Bump the version file** to the intended release version: `version = \"x.y.z\"`\n in `pyproject.toml` (packages) or the `VERSION` file (services/sites).\n\n---\n\n## 🏷️ Tag the merged commit\n\n1. **Merge or squash** the MR into `main`.\n2. **Create the tag** on the resulting commit: `git tag x.y.z` (lightweight).\n3. **Push the tag** (`git push origin x.y.z`) — Drone builds the release.\n\n### 🤖 Agent-assisted tagging\n\nThe agent does not author or merge the MR and never tags before the merge. After\nyou confirm the MR is merged/squashed and tell it to proceed, the agent:\n\n1. Waits for that explicit go-ahead.\n2. Finds the commit that introduced the bump:\n - package:\n `git log -S 'version = \"x.y.z\"' -n 1 --format='%H %s' -- pyproject.toml`\n - service / site:\n `git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`\n3. Verifies that commit's version file equals `x.y.z`, then tags **only** that\n commit: `git tag x.y.z <sha>`.\n4. Does **not** push — it asks you to run `git push origin x.y.z`.\n\n---\n\n## 🤖 What Drone does\n\n- **Type A (package):** asserts `tag == pyproject version`, then skips if that\n version already exists on the index, runs the quality gate, builds, and\n uploads the wheel + sdist.\n- **Type B (service):** asserts `tag == VERSION`, resolves the tag, skips if the\n image already exists, runs the quality gate, builds, pushes, and restarts the\n container.\n- **Type B (site):** triggered on push, ships `:latest` and has no tag guard.\n\nIf the guard fails, nothing is published — fix the version file, amend the\ncommit, and move the tag **only** if it was never pushed.\n\n---\n\n## 🛟 Backfill / recovery\n\nIf an older tag never published (a past version-file mismatch, an index\noutage):\n\n- **Package:** run the `backfill-pypi-from-tags` pipeline (custom event). It\n checks out every tag, verifies `tag == pyproject version` at that commit,\n and uploads the ones missing from the index. Already-fixed history is never\n rewritten.\n- **Service:** rebuild the image for the missing tag with a fresh commit and a\n new patch version; do not move a published tag.\n\n---\n\n## 🔁 If a release is wrong\n\n- Never reuse or force-move a pushed tag.\n- Fix forward: bump to the next patch, promote the changelog, tag again.\n\n---\n\n## Related\n\n- [Testing & Quality Gates](01_testing.md) · [Versioning & Changelog](../lib/11_versioning_and_changelog/) · [Drone Pipelines](../lib/10_drone_pipelines/)\n",
"objects": {}
}
}

View File

@@ -0,0 +1,8 @@
{
"module": "wiki_development_testing",
"content": {
"path": "wiki_development_testing",
"docstring": "# Testing & Quality Gates\n\nTwo suites run under pytest with asyncio auto mode: **integration** (the HTTP\nedge against fakes) and **unit** (pure domain and pipeline logic). Finish a\nblueprint app only when every gate below passes.\n\n---\n\n## 🗂️ Layout\n\n```\ntests/\n integration/ # TestClient(app) + mocked resources\n unit/ # models, converters, pipeline stages/roots\n conftest.py # fixtures\n```\n\n---\n\n## 🚀 Integration tests\n\n- Build the app with `TestClient`.\n- Replace `app.state.db_resources` with fake repositories/services so no database\n is required.\n- Patch the auth introspection call (`httpx.AsyncClient.post`) to return a\n canned identity.\n- Assert status codes and bodies for list/get/create/update/delete, plus\n `400`, `404`, `204`, and the streaming endpoint's fallback.\n\n```python\nclient = TestClient(app)\napp.state.db_resources = {\"item_svc\": FakeItemService(), ...}\n\ndef test_create_item():\n r = client.post(\"/items\", json={\"name\": \"x\"})\n assert r.status_code == 200\n```\n\n---\n\n## 🧪 Unit tests\n\n- Domain models and converters: pure, no I/O.\n- Pipeline stages: feed a fixture state, assert the transformed state.\n- Assembled pipeline roots: **fixture parity** — replay a stored fixture input\n and compare against a golden output file.\n\n---\n\n## 🔐 Config-consistency tests\n\nFor rule-driven pipelines, add a test that cross-references rules against the\ndeclared pattern registry: every flag emitted must be defined; every defined\nflag must be reachable. This catches drift without running the pipeline.\n\n---\n\n## ✅ Quality gates\n\nA **definition of done** for any standard app:\n\n- `python -c \"import main\"` succeeds.\n- `pytest` passes.\n- The app serves the spec's operations; the worker drains its queue.\n- No domain module imports a web framework.\n- No infrastructure was reimplemented that a library already provides.\n\n---\n\n## 💡 Conventions\n\n- `asyncio_mode = \"auto\"`; no manual event loops.\n- Fixtures are checked into the repo (inputs + golden outputs).\n- Tests skip (not fail) when a required fixture is absent.\n- Do not mock the domain; mock only the infrastructure edge.\n\n---\n\n## Related\n\n- [02 – App Components](../02_components.md) · [03 – Start an App](../03_start_an_app.md) · [Overview](../01_overview.md)\n",
"objects": {}
}
}

View File

@@ -0,0 +1,8 @@
{
"module": "wiki_index",
"content": {
"path": "wiki_index",
"docstring": "# 🧩 Standards — the Aetos App Blueprint\n\nOne reusable architecture for every backend application in the Aetos ecosystem:\na spec-first HTTP edge, a framework-free domain core, a pluggable infrastructure\nlayer, and a single composition root.\n\nFill in **two axes only** and a new app is done: your **resource model**, and\n(if needed) your **pipeline family**. The blueprint itself never names a\nbusiness domain — expenses, health metrics, inventory, and analytics are all\nthe same shape underneath.\n\n> **Doc model:** this wiki is written for humans — how-to guides, the build\n> recipe, and quality gates. The enforceable rules (layering, composition root,\n> CRUD template, pipeline scaffold) and the library registry live in the\n> **reference** pages, mirrored by the machine-readable `standards` MCP bundle.\n> Query `find_library` before writing any infrastructure.\n\n---\n\n## 🚀 Key Features\n\n- 🧭 **Spec-first routing** — the OpenAPI document, not decorator routers, is\n the source of truth (operationId lookup)\n- 🏗️ **One-way layering** — `interfaces → domain → infra`, enforced\n- 🔌 **Single composition root** — every dependency is wired in exactly one place\n- 🧩 **Generic, not app-specific** — reusable rules versus two authoring axes\n- 📚 **Library-backed** — DB, DI, pipelines, ingestion, auth, and docs come from\n internal libraries, never hand-written\n- 🚦 **Explicit state machines** — durable job statuses, progress, and\n human-in-the-loop pauses\n\n---\n\n## 📁 Documentation Structure\n\n| Section | Description |\n|-----------------------------------------------------|--------------------------------------------------------|\n| [Overview](01_overview.md) | Philosophy, the one-way dependency rule, architecture |\n| [App Components](02_components.md) | The component model and how the pieces wire together |\n| [Start an App](03_start_an_app.md) | The entry point: gather the two axes, then build |\n| [Testing & Quality](04_development/01_testing.md) | Test layout and acceptance gates |\n\n**Reference** (separate site, mirrors the MCP bundle): the enforceable\nrules — [Build Recipe](../lib/01_build_recipe/), [Layering](../lib/02_layering_rules/), [Composition\nRoot](../lib/03_composition_root/), [Spec-First\nRouting](../lib/04_spec_first_routing/), [CRUD\nTemplate](../lib/05_crud_template/), [Pipeline\nScaffold](../lib/06_pipeline_scaffold/), [Worker &\nProgress](../lib/07_worker_and_progress/), [Human-in-the-Loop](../lib/08_human_in_loop/),\n[Infra Adapters](../lib/09_infra_adaptors/), [Genericity\nMatrix](../lib/12_genericity_matrix/), [Library\nRegistry](../lib/13_library_registry/).\n\n---\n\n## 🔗 Related Resources\n\n- **MCP bundle** — `standards` server on `:8009`: read `docs://index`,\n `docs://nav`, `docs://modules/{module}`; call `find_library(capability)`\n- **Library registry** — the catalog of internal libraries behind every app\n (`libraries.json`, served as `registry://libraries`)\n- **Template** — the runnable skeleton at `standards/src/templates/app/`\n- **Start a build** — invoke the `start_app` prompt on the `standards` server,\n or read the [Start an App](03_start_an_app.md) guide\n\n---\n\n© Aetoskia Internal — `standards`\n",
"objects": {}
}
}

View File

@@ -0,0 +1,8 @@
{
"module": "wiki_overview",
"content": {
"path": "wiki_overview",
"docstring": "# Overview\n\nThe blueprint is a **hexagonal (ports & adapters) application** with a\n**spec-first** web edge and a **single composition root**. It favors explicit\nwiring over implicit magic and existing internal libraries over hand-written\ninfrastructure. Start here to build the mental model; the enforceable details\nlive in the reference pages.\n\n---\n\n## 🏗️ Architecture\n\n```mermaid\nflowchart LR\n subgraph EDGE[\"interfaces/ — HTTP edge\"]\n OAS[\"openapi.yaml<br/>operationId contract\"]\n RT[\"<resource>/routes.py<br/>thin handlers\"]\n end\n\n subgraph CORE[\"domain/ — business core\"]\n MD[\"models\"]\n PT[\"ports (adapter binding seam)\"]\n SV[\"services\"]\n end\n\n subgraph INFRA[\"infra/ — adapters\"]\n RP[\"mongo repos (mongo-ops)\"]\n AD[\"cache · stores · llm · mail\"]\n DB[(\"MongoDB\")]\n end\n\n CR[\"init.py<br/>composition root\"]\n\n OAS --> RT\n RT --> SV\n SV --> PT\n SV --> RP\n PT -. depends on .- RP\n CR --- RT\n CR --- SV\n CR --- RP\n RP --> DB\n AD --- CR\n```\n\nThe rule holds at every edge: code in an outer ring may depend on a ring closer\nto the core, never the reverse. `domain/` never imports a web framework;\nonly its `ports/` seam reaches into `infra/` to bind adapters.\n\n> **Legend** — solid arrows are compile-time calls; dotted arrows are the\n> port/adapter binding, performed by the composition root at startup.\n\n---\n\n## 🔄 The one-way dependency rule\n\n| Ring | May import | Never imports |\n|-------------|------------------------------------|----------------------------------|\n| `interfaces/` | `domain`, framework glue (glue only) | business logic, `infra` directly |\n| `domain/` | its own models + its `ports/` seam (which binds `infra`/libraries) | web frameworks, `interfaces/` |\n| `infra/` | `domain` ports, drivers, libraries | — |\n| `init.py` | everything (the wiring point) | — |\n\n---\n\n## 🧭 Core principles\n\n1. **One-way dependencies.** `interfaces -> domain -> infra` is allowed; the\n reverse is forbidden.\n2. **Ports in the domain, adapters in infra.** A service depends on a port; the\n port's factory binds the concrete adapter, and the composition root calls\n those factories.\n3. **One composition root.** Nothing constructs its own dependencies. All\n wiring lives in one module (`init.py`) exposed as a flat resource registry.\n4. **Spec-first.** The HTTP contract is declared in `openapi.yaml`; route\n handlers are looked up by `operationId`. There are no decorator routers.\n5. **Reuse the libraries.** Orchestration, DI, Mongo repositories, ingestion,\n auth, and docs come from internal libraries — see the [Library\n Registry](../lib/13_library_registry/).\n6. **Explicit state machines.** Long-running work moves through explicit,\n durable statuses and can pause for human input.\n\n---\n\n## ⚙️ Runtime processes\n\nA standard app may run as up to three processes that share the same composition\nroot:\n\n| Process | Entry point | Responsibility |\n|---------|------------------------|-------------------------------------------|\n| API | `main.py` | serve HTTP; start/stop resources via lifespan |\n| Worker | `worker.py` | poll durable jobs, run pipelines, publish progress |\n| Scripts | `python -m scripts.<name>` | one-shot operator tasks |\n\n---\n\n## ➡️ Read Next\n\n- [02 – App Components](02_components.md) · [03 – Start an App](03_start_an_app.md)\n",
"objects": {}
}
}

View File

@@ -0,0 +1,8 @@
{
"module": "wiki_start_an_app",
"content": {
"path": "wiki_start_an_app",
"docstring": "# 🚀 Start an App\n\nThis is the entry point for building a new application from the blueprint. The\ngoal is one deterministic pass: you supply only the **two axes**, and the agent\ndoes the rest by reading the `standards` MCP, importing Tier A libraries, and\ncopying Tier B template components.\n\n> **Doc model:** this page is the human entry point. The deterministic,\n> machine-facing checklist lives in the reference page\n> [Build Recipe](../lib/01_build_recipe/), served as `docs://modules/build_recipe`.\n\n---\n\n## 🧾 What the agent needs from you\n\nGather these before starting — the `start_app` MCP prompt asks for them:\n\n1. **App name + one-line purpose.**\n2. **Axis 1 — resources.** Each resource, its fields, and relationships. Paste\n `openapi.yaml` if you already have it.\n3. **Axis 2 — pipelines.** Only if an input needs a multi-step transformation:\n the families and their input formats (or \"none\").\n4. **Auth needed now?** Default **no**. Auth is deferred (`py-jwt` primitives\n only), and an authless app is just a spec with no `security` keys — see\n [Spec-First Routing → Authless apps](../lib/04_spec_first_routing/#authless-apps).\n5. **Integrations.** Mongo, ingestion formats, email, LLM provider.\n\nIf you can state axes 1 and 2, the rest is mechanical.\n\n---\n\n## ▶️ How to start\n\nFrom an MCP client connected to the `standards` server:\n\n1. Invoke the **`intake`** prompt (or `start_app` with no arguments) to get the\n intake questions.\n2. Answer them, then invoke **`build`**\n (`build(app_name=..., resources=..., pipelines=..., auth=...)`), or use\n **`start_app`** to intake-and-build in one call.\n3. The prompt hands off to `docs://modules/build_recipe`; the agent scaffolds\n with `get_app_template()` and pulls exact files with\n `get_app_component(kind, resource=..., family=...)`, authoring only the\n app-specific parts.\n4. To validate the standard without touching a real app, invoke **`trial`**\n (`trial(app_name=..., build_path=...)`) to build into a scratch directory,\n prove it builds, and report findings.\n\nManual alternative — copy the skeleton and follow the reference recipe:\n\n```bash\ncp -r <docs>/standards/src/templates/app apps/<your-app>\n```\n\n---\n\n## 📚 Reference\n\n- [Build Recipe](../lib/01_build_recipe/) — the deterministic checklist\n- [Genericity Matrix](../lib/12_genericity_matrix/) — Tier A / B / C\n- [Library Registry](../lib/13_library_registry/) — the Tier A catalog\n- [App Components](02_components.md) — the component model\n\n---\n\n## Related\n\n- [01 – Overview](01_overview.md) · [02 – App Components](02_components.md) · [04 – Testing & Quality](04_development/01_testing.md)\n",
"objects": {}
}
}

View File

@@ -49,6 +49,11 @@
"resource": "docs://modules/drone_pipelines",
"title": "Drone Pipelines"
},
{
"module": "versioning_and_changelog",
"resource": "docs://modules/versioning_and_changelog",
"title": "Versioning & Changelog"
},
{
"module": "genericity_matrix",
"resource": "docs://modules/genericity_matrix",
@@ -64,9 +69,44 @@
"resource": "docs://modules/auth_integration",
"title": "Auth Integration"
},
{
"module": "repository_inventory",
"resource": "docs://modules/repository_inventory",
"title": "Repository Inventory"
},
{
"module": "index",
"resource": "docs://modules/index",
"title": "Standards Reference"
},
{
"module": "wiki_overview",
"resource": "docs://modules/wiki_overview",
"title": "Overview"
},
{
"module": "wiki_components",
"resource": "docs://modules/wiki_components",
"title": "App Components"
},
{
"module": "wiki_start_an_app",
"resource": "docs://modules/wiki_start_an_app",
"title": "🚀 Start an App"
},
{
"module": "wiki_development_testing",
"resource": "docs://modules/wiki_development_testing",
"title": "Testing & Quality Gates"
},
{
"module": "wiki_development_releasing",
"resource": "docs://modules/wiki_development_releasing",
"title": "Releasing & Versioning"
},
{
"module": "wiki_index",
"resource": "docs://modules/wiki_index",
"title": "🧩 Standards — the Aetos App Blueprint"
}
]

View File

@@ -0,0 +1,19 @@
{
"project": "standards",
"template": "templates/release",
"invariant": "git tag == version file (enforced by the Drone guard)",
"components": [
{
"kind": "changelog",
"path": "CHANGELOG.md",
"placeholder": "",
"description": "Keep a Changelog skeleton: one [Unreleased] section + compare links."
},
{
"kind": "version-file",
"path": "VERSION",
"placeholder": "",
"description": "Single-line SemVer source of truth for services and sites."
}
]
}

169
standards/mcp/repos.json Normal file
View File

@@ -0,0 +1,169 @@
{
"project": "standards",
"note": "Machine-readable repository inventory. Mirrors standards/src/lib/15_repository_inventory.md. Read it with the registry://repos resource or the list_repos tool.",
"path_convention": "relative to the aetos/ workspace root",
"repos": [
{
"name": "dagpipe",
"type": "package",
"path": "dagpipe",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": [
"lib",
"wiki"
],
"mcp": {
"server": "dagpipe",
"port": 8006
}
},
{
"name": "doc-forge",
"type": "package",
"path": "doc-forge",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": [
"lib",
"wiki"
],
"mcp": {
"server": "docforge",
"port": 8001
}
},
{
"name": "hexa",
"type": "package",
"path": "hexa",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": [
"lib",
"wiki"
],
"mcp": {
"server": "hexa",
"port": 8008
}
},
{
"name": "mail-intake",
"type": "package",
"path": "mail-intake",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": [
"lib",
"wiki"
],
"mcp": {
"server": "mail_intake",
"port": 8002
}
},
{
"name": "mongo-ops",
"type": "package",
"path": "mongo-ops",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": [
"lib",
"wiki"
],
"mcp": {
"server": "mongo_ops",
"port": 8007
}
},
{
"name": "omniread",
"type": "package",
"path": "omniread",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": [
"lib",
"wiki"
],
"mcp": {
"server": "omniread",
"port": 8003
}
},
{
"name": "openapi-first",
"type": "package",
"path": "openapi-first",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": [
"lib",
"wiki"
],
"mcp": {
"server": "openapi_first",
"port": 8004
}
},
{
"name": "py-jwt",
"type": "package",
"path": "py-jwt",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": [
"lib",
"wiki"
],
"mcp": {
"server": "jwtlib",
"port": 8005
}
},
{
"name": "auth-server",
"type": "service",
"path": "auth-server",
"version_source": "VERSION",
"drone": "service-docker.yaml",
"trigger": "tag",
"docs": [
"api",
"lib",
"wiki"
],
"mcp": null
},
{
"name": "docs",
"type": "site",
"path": "docs",
"version_source": "VERSION",
"drone": "service-docker.yaml",
"trigger": "push",
"docs": [],
"mcp": null
},
{
"name": "mcp",
"type": "site",
"path": "mcp",
"version_source": "VERSION",
"drone": "service-docker.yaml",
"trigger": "push",
"docs": [],
"mcp": null
}
]
}

View File

@@ -0,0 +1,22 @@
# Changelog
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
### Changed
### Fixed
## [0.1.0] - YYYY-MM-DD
### Added
- Initial blueprint app scaffolded from the Aetos standard.
[Unreleased]: <repo-url>/compare/0.1.0...HEAD
[0.1.0]: <repo-url>/releases/tag/0.1.0

View File

@@ -12,6 +12,10 @@
# Secrets (Drone): PIP_REPO_URL, PIP_USERNAME, PIP_PASSWORD.
# Copy to <repo>/.drone.yml unchanged; the package name and version are read
# from pyproject.toml, so no per-repo edits are required.
#
# Version guard: the tag build fails unless the git tag equals the version in
# pyproject.toml (see the versioning standard). This stops a tag from looking
# like a release while silently publishing nothing.
---
kind: pipeline
type: docker
@@ -37,6 +41,11 @@ steps:
commands:
- PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
- VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
- |
if [ "$DRONE_TAG" != "$VERSION" ]; then
echo "Tag '$DRONE_TAG' does not match pyproject version '$VERSION'; refusing to publish."
exit 1
fi
- echo "Checking if $PACKAGE_NAME==$VERSION exists on $PIP_REPO_URL ..."
- |
if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then
@@ -147,6 +156,12 @@ steps:
git checkout --force "$TAG"
TAG_VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
if [ "$TAG" != "$TAG_VERSION" ]; then
echo "Tag $TAG != pyproject version $TAG_VERSION at that commit; skipping"
continue
fi
echo "Building $VERSION"
rm -rf dist
python -m build

View File

@@ -20,6 +20,10 @@
# <host-port> published host port; <container-port> the in-image port.
# <site> site image name for the site variant (e.g. docs, mcp).
# <env-var> runtime environment variable consumed by the service.
#
# Version guard: the tag-triggered service variant fails unless the git tag
# equals the repo-root VERSION file (see the versioning standard). The
# push-triggered site variant ships :latest only and has no version to guard.
---
kind: pipeline
type: docker
@@ -64,6 +68,22 @@ steps:
exit 1
fi
- name: check-version
image: alpine:3.20
commands:
- VERSION=$(cat VERSION 2>/dev/null | tr -d '\n')
- echo "tag=$DRONE_TAG version=$VERSION"
- |
if [ -z "$VERSION" ]; then
echo "VERSION file missing or empty; add it (see versioning standard)."
exit 1
fi
- |
if [ "$DRONE_TAG" != "$VERSION" ]; then
echo "Tag '$DRONE_TAG' does not match VERSION '$VERSION'; refusing to deploy."
exit 1
fi
- name: check-remote-image
image: docker:24
volumes:

View File

@@ -0,0 +1,37 @@
# Changelog
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
<!--
In the release merge request, promote the entries above into a new heading and
leave a fresh, empty [Unreleased] section. Name the version/tag, never a commit
id:
## [0.1.0] - YYYY-MM-DD
### Added
- Initial release.
Then update the compare links below. The tag is created on the merged commit —
see the standard: Versioning & Changelog (registry://release).
-->
[Unreleased]: <repo-url>/compare/0.1.0...HEAD
[0.1.0]: <repo-url>/releases/tag/0.1.0

View File

@@ -0,0 +1 @@
0.0.0

View File

@@ -25,9 +25,10 @@ needs no edits, the Type B file needs its placeholders filled in (below).
For internal libraries. The file contains **two pipelines**:
1. **`build-and-publish-pypi`** — triggered on `tag`:
- `check-version` — read `name` / `version` from `pyproject.toml` and query
- `check-version` — **guard**: fail unless `$DRONE_TAG` equals the version in
`pyproject.toml`; then read `name` / `version` and query
`$PIP_REPO_URL/simple/<package>/`; if that version already exists, exit
`78` and skip the whole build.
`78` and skip the whole build (see [11 – Versioning & Changelog](11_versioning_and_changelog.md)).
- `quality-gate` — install the package with its `[dev]` extras from the
private index, then `black --check`, `ruff check`, `mypy`, `pydoclint`,
`pytest`.
@@ -36,8 +37,9 @@ For internal libraries. The file contains **two pipelines**:
2. **`backfill-pypi-from-tags`** — triggered on `custom`:
- `fetch-tags` — `git fetch --tags --force`.
- `build-and-upload-missing` — walk every tag in version order, skip the ones
already on the index, `git checkout` the rest, build, and upload. This is
the disaster-recovery / replay path.
already on the index, `git checkout` the rest, verify `tag == pyproject
version` at that commit, build, and upload. This is the disaster-recovery /
replay path.
Secrets: `PIP_REPO_URL`, `PIP_USERNAME`, `PIP_PASSWORD`.
@@ -55,6 +57,8 @@ pipelines**, each mirroring a repo family:
API/worker service, e.g. `auth-server`):
- `fetch-tags` — resolve the latest tag into `LATEST_TAG.txt`; fail if there
are no tags.
- `check-version` — **guard**: fail unless `$DRONE_TAG` equals the repo-root
`VERSION` file (see [11 – Versioning & Changelog](11_versioning_and_changelog.md)).
- `check-remote-image` — if `aetos/<app>:<tag>` already exists locally, exit
`78` and skip the build.
- `quality-gate` — install from `requirements.txt` and run
@@ -67,7 +71,8 @@ pipelines**, each mirroring a repo family:
--restart always` with the service's `-e` environment.
2. **`build-and-deploy-site`** — triggered on `push` (a static/HTTP site, e.g.
`docs`, `mcp`): `build-image` → `push-image` → `stop-old` → `run-container`,
with no tag resolution and no quality gate.
with no tag resolution, no quality gate, and no version guard (it ships
`:latest` only).
Secrets: `PIP_REPO_URL`, `PIP_USERNAME`, `PIP_PASSWORD` (service build args),
`REGISTRY_HOST`, `REGISTRY_USER`, `REGISTRY_PASS`, plus the service runtime env
@@ -91,6 +96,9 @@ Placeholders to fill in per repo:
- Do not inline secrets; always use `from_secret`.
- Do not drop the `check-version` / `check-remote-image` skip — it guards the
index and the running container against duplicate publishes.
- Do not drop the tag↔version guard. A package needs its `pyproject.toml`
version to equal the tag; a service needs a root `VERSION` file that equals
the tag. See [11 – Versioning & Changelog](11_versioning_and_changelog.md).
- Do not rename the pipelines; tooling and runbooks key off
`build-and-publish-pypi`, `build-and-deploy-service`, and
`build-and-deploy-site`.
@@ -99,4 +107,4 @@ Placeholders to fill in per repo:
## Related
- [01 – Build Recipe](01_build_recipe.md) · [09 – Infra Adapters](09_infra_adaptors.md) · [13 – Library Registry](13_library_registry.md)
- [01 – Build Recipe](01_build_recipe.md) · [09 – Infra Adapters](09_infra_adaptors.md) · [11 – Versioning & Changelog](11_versioning_and_changelog.md) · [13 – Library Registry](13_library_registry.md)

View File

@@ -0,0 +1,157 @@
# Versioning & Changelog
## Summary
Every Aetos artifact — library, service, or site — is versioned with
[Semantic Versioning](https://semver.org/spec/v2.0.0.html) and documents its
releases in a [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) file.
The **version file is the source of truth**, the **git tag must equal it**, and
a Drone guard enforces that equality before anything is published. Releases are
cut manually; a tag is what triggers the pipeline.
This page pairs with [10 – Drone Pipelines](10_drone_pipelines.md): the version
and tag contract is exactly what those two CI shapes consume.
---
## The version contract
| Artifact | Source of truth | Notes |
|----------|-----------------|-------|
| Library (PyPI package) | `version` in `pyproject.toml` | `check-version` also reads it to skip duplicates |
| Service / site (Docker) | `VERSION` file at the repo root | one line, `x.y.z`; the service **reads** it (build-arg) — never hardcode the version |
Rules:
1. **One version per repository.** No per-module versions.
2. **Tag = version.** The git tag is bare SemVer (`0.0.6`), no `v` prefix, and
it MUST match the version file byte-for-byte. The Drone guard fails the
build otherwise — this is what stops a tag from silently publishing nothing.
3. **A release is a tag, created after the change is merged.** The version bump
and the changelog promotion travel inside the merge request (MR); the tag is
added on the merged commit and pushed. See [Tagging strategy](#tagging-strategy-mr-based).
4. **Never reuse a tag.** If a release is wrong, cut a new patch version.
### Pre-1.0 (0.y.z)
While a package is `0.y.z`, **`0.y` is treated as the major line**:
- breaking change → `0.(y+1).0`
- new feature → `0.y.(z+1)`
- bug fix → `0.y.(z+1)`
Once a package reaches `1.0.0`, normal SemVer applies. Do not ship `1.0.0`
until the public API is considered stable.
---
## The changelog
`CHANGELOG.md` at the repo root, in Keep a Changelog form. It always has exactly
one open `[Unreleased]` section with the change categories that apply:
```markdown
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
```
On release you **promote** it — never delete history:
```markdown
## [0.0.6] - 2026-03-07
### Added
- ...
```
and add a link reference at the bottom:
```markdown
[Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/0.0.6...HEAD
[0.0.6]: https://git.aetoskia.com/aetos/<repo>/compare/0.0.5...0.0.6
```
Conventions:
- Write entries for humans: one line each, imperative, referencing the user
impact, not the commit hash.
- Categories: **Added**, **Changed**, **Deprecated**, **Removed**, **Fixed**,
**Security**. Omit empty categories.
- Security fixes get their own entry even if the change is otherwise a bug fix.
- The `[Unreleased]` heading is never renamed; copy its content into the new
version heading and clear it.
---
## Tagging strategy (MR-based)
The release version is known while the change is still a merge request, so the
bump travels with the change and only the tag is added afterwards:
1. **In the MR** — set the intended release version: `version` in
`pyproject.toml` (or the `VERSION` file) to `x.y.z`, and promote the
changelog (below). Changelog entries and link references name the
**version/tag**, never a commit id.
2. **Merge or squash** the MR into `main`.
3. **Create the tag** `x.y.z` on the resulting commit (the merge/squash commit).
4. **Push the tag** (`git push origin x.y.z`) — Drone builds the release.
Because the version bump is inside the tagged commit, the guard's
`tag == version` check can only fail if the wrong commit was tagged.
### Agent-assisted tagging
The agent neither authors nor merges the MR, and never tags before the merge.
After the user confirms the MR is merged/squashed and says to proceed:
1. **Wait** for that explicit go-ahead.
2. Find the commit that introduced the version bump:
- packages:
`git log -S 'version = "x.y.z"' -n 1 --format='%H %s' -- pyproject.toml`
- services:
`git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`
3. Confirm that commit's version file equals `x.y.z`, then tag **only** that
commit, lightweight and bare: `git tag x.y.z <sha>`.
4. **Do not push.** Ask the user to push the tag: `git push origin x.y.z`.
---
## Release checklist
1. Confirm the quality gate is green and `main` is up to date.
2. In the MR: move every `[Unreleased]` entry under the new version heading with
the target date (`## [x.y.z] - YYYY-MM-DD`); restore an empty `[Unreleased]`
on top.
3. In the MR: add the two compare-link references at the bottom of the
changelog, naming the version/tag (never a commit id).
4. In the MR: set the version file — `pyproject.toml` (`version = "x.y.z"`) or
`VERSION` — to the intended release version.
5. Merge or squash the MR into `main`.
6. Create the tag on the resulting commit: lightweight `git tag x.y.z`.
7. Push the tag (`git push origin x.y.z`). Watch Drone: Type A publishes the
wheel/sdist, Type B builds and deploys the image; both first assert
`tag == version`.
8. Do not reuse the tag if the run fails — fix and cut `x.y.(z+1)`.
---
## Why the guard matters
The version file and the tag are two facts that must agree; nothing in git
enforces that by itself. A mismatch is not a theoretical risk — a package can be
tagged `0.0.2` while its `pyproject.toml` still says `0.0.1`, and the publish
step (which compares the **file** against the index) then skips the build
entirely, so `0.0.2` never reaches the index while looking like a release. The
Drone guard turns that silent no-op into a hard failure.
---
## Related
- [10 – Drone Pipelines](10_drone_pipelines.md) · [15 – Repository Inventory](15_repository_inventory.md) · [01 – Build Recipe](01_build_recipe.md) · [13 – Library Registry](13_library_registry.md)

View File

@@ -0,0 +1,74 @@
# Repository Inventory
## Summary
The canonical list of Aetos repositories covered by the CI and versioning
standards, with the artifact each one produces, its version source, and its
Drone shape. Every entry maps to the contract in
[11 – Versioning & Changelog](11_versioning_and_changelog.md) and the two CI
files in [10 – Drone Pipelines](10_drone_pipelines.md).
The same data is published machine-readably as the `registry://repos` MCP
resource (and the `list_repos` tool), sourced from `standards/src/repos.json`.
Paths are relative to the `aetos/` workspace root.
---
## Packages (Type A)
A wheel + sdist published to the private index. Version source is
`pyproject.toml`; Drone file is `python-lib.yaml` (tag-triggered).
| Repo | Path | Docs | MCP server |
|------|------|------|------------|
| `dagpipe` | `dagpipe` | lib, wiki | `dagpipe:8006` |
| `doc-forge` | `doc-forge` | lib, wiki | `docforge:8001` |
| `hexa` | `hexa` | lib, wiki | `hexa:8008` |
| `mail-intake` | `mail-intake` | lib, wiki | `mail_intake:8002` |
| `mongo-ops` | `mongo-ops` | lib, wiki | `mongo_ops:8007` |
| `omniread` | `omniread` | lib, wiki | `omniread:8003` |
| `openapi-first` | `openapi-first` | lib, wiki | `openapi_first:8004` |
| `py-jwt` | `py-jwt` | lib, wiki | `jwtlib:8005` |
---
## Services & sites (Type B)
A container image pushed to the registry and run on the Docker host. Version
source is the `VERSION` file; Drone file is `service-docker.yaml`.
| Repo | Type | Path | Trigger | Docs |
|------|------|------|---------|------|
| `auth-server` | service | `auth-server` | tag | api, lib, wiki |
| `docs` | site | `docs` | push | — |
| `mcp` | site | `mcp` | push | — |
The **service** variant is tag-triggered and enforces `tag == VERSION`. The
**site** variant is push-triggered, ships `:latest`, and has no tag guard.
---
## Required files per repo
| Type | Version source | Changelog | Drone |
|------|----------------|-----------|-------|
| package | `pyproject.toml` | `CHANGELOG.md` | `python-lib.yaml` |
| service / site | `VERSION` | `CHANGELOG.md` | `service-docker.yaml` |
Every covered repository must be able to satisfy `tag == version`:
the version file (never a commit id) is bumped in the release MR, the MR is
merged, and the tag is created on the merged commit.
---
## Reading this at runtime
- Resource: `registry://repos`
- Tool: `list_repos()`
- Humans: this page (mirror of `standards/src/repos.json`).
---
## Related
- [10 – Drone Pipelines](10_drone_pipelines.md) · [11 – Versioning & Changelog](11_versioning_and_changelog.md) · [13 – Library Registry](13_library_registry.md)

View File

@@ -15,6 +15,10 @@ patterns, and the internal library catalog that agents consult while coding.
- **Human-in-the-Loop** — guardrails and approvals inside automations.
- **Infra Adapters** — how infrastructure connects without leaking into domain.
- **Drone Pipelines** — the two CI shapes: a PyPI package or a Docker deployment.
- **Versioning & Changelog** — SemVer, the tag↔version contract, and the release
changelog.
- **Repository Inventory** — every covered repo, its type, version source, and
Drone shape.
- **Genericity Matrix** — what is library-backed, template-provided, or authored.
- **Auth Integration** — consuming the shared auth-server service (spec-driven,
no auth code).

View File

@@ -64,6 +64,8 @@ nav:
- Human-in-the-Loop: 08_human_in_loop.md
- Infra Adapters: 09_infra_adaptors.md
- Drone Pipelines: 10_drone_pipelines.md
- Versioning & Changelog: 11_versioning_and_changelog.md
- Repository Inventory: 15_repository_inventory.md
- Genericity Matrix: 12_genericity_matrix.md
- Auth Integration: 14_auth_integration.md
- Library Registry: 13_library_registry.md

View File

@@ -67,4 +67,5 @@ nav:
- Overview: 01_overview.md
- App Components: 02_components.md
- Start an App: 03_start_an_app.md
- Testing & Quality: 04_development/01_testing.md
- Testing & Quality: 04_development/01_testing.md
- Releasing & Versioning: 04_development/02_releasing.md

117
standards/src/repos.json Normal file
View File

@@ -0,0 +1,117 @@
{
"project": "standards",
"note": "Machine-readable repository inventory. Mirrors standards/src/lib/15_repository_inventory.md. Read it with the registry://repos resource or the list_repos tool.",
"path_convention": "relative to the aetos/ workspace root",
"repos": [
{
"name": "dagpipe",
"type": "package",
"path": "dagpipe",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": ["lib", "wiki"],
"mcp": { "server": "dagpipe", "port": 8006 }
},
{
"name": "doc-forge",
"type": "package",
"path": "doc-forge",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": ["lib", "wiki"],
"mcp": { "server": "docforge", "port": 8001 }
},
{
"name": "hexa",
"type": "package",
"path": "hexa",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": ["lib", "wiki"],
"mcp": { "server": "hexa", "port": 8008 }
},
{
"name": "mail-intake",
"type": "package",
"path": "mail-intake",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": ["lib", "wiki"],
"mcp": { "server": "mail_intake", "port": 8002 }
},
{
"name": "mongo-ops",
"type": "package",
"path": "mongo-ops",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": ["lib", "wiki"],
"mcp": { "server": "mongo_ops", "port": 8007 }
},
{
"name": "omniread",
"type": "package",
"path": "omniread",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": ["lib", "wiki"],
"mcp": { "server": "omniread", "port": 8003 }
},
{
"name": "openapi-first",
"type": "package",
"path": "openapi-first",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": ["lib", "wiki"],
"mcp": { "server": "openapi_first", "port": 8004 }
},
{
"name": "py-jwt",
"type": "package",
"path": "py-jwt",
"version_source": "pyproject.toml",
"drone": "python-lib.yaml",
"trigger": "tag",
"docs": ["lib", "wiki"],
"mcp": { "server": "jwtlib", "port": 8005 }
},
{
"name": "auth-server",
"type": "service",
"path": "auth-server",
"version_source": "VERSION",
"drone": "service-docker.yaml",
"trigger": "tag",
"docs": ["api", "lib", "wiki"],
"mcp": null
},
{
"name": "docs",
"type": "site",
"path": "docs",
"version_source": "VERSION",
"drone": "service-docker.yaml",
"trigger": "push",
"docs": [],
"mcp": null
},
{
"name": "mcp",
"type": "site",
"path": "mcp",
"version_source": "VERSION",
"drone": "service-docker.yaml",
"trigger": "push",
"docs": [],
"mcp": null
}
]
}

View File

@@ -0,0 +1,22 @@
# Changelog
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
### Changed
### Fixed
## [0.1.0] - YYYY-MM-DD
### Added
- Initial blueprint app scaffolded from the Aetos standard.
[Unreleased]: <repo-url>/compare/0.1.0...HEAD
[0.1.0]: <repo-url>/releases/tag/0.1.0

View File

@@ -12,6 +12,10 @@
# Secrets (Drone): PIP_REPO_URL, PIP_USERNAME, PIP_PASSWORD.
# Copy to <repo>/.drone.yml unchanged; the package name and version are read
# from pyproject.toml, so no per-repo edits are required.
#
# Version guard: the tag build fails unless the git tag equals the version in
# pyproject.toml (see the versioning standard). This stops a tag from looking
# like a release while silently publishing nothing.
---
kind: pipeline
type: docker
@@ -37,6 +41,11 @@ steps:
commands:
- PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
- VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
- |
if [ "$DRONE_TAG" != "$VERSION" ]; then
echo "Tag '$DRONE_TAG' does not match pyproject version '$VERSION'; refusing to publish."
exit 1
fi
- echo "Checking if $PACKAGE_NAME==$VERSION exists on $PIP_REPO_URL ..."
- |
if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then
@@ -147,6 +156,12 @@ steps:
git checkout --force "$TAG"
TAG_VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
if [ "$TAG" != "$TAG_VERSION" ]; then
echo "Tag $TAG != pyproject version $TAG_VERSION at that commit; skipping"
continue
fi
echo "Building $VERSION"
rm -rf dist
python -m build

View File

@@ -20,6 +20,10 @@
# <host-port> published host port; <container-port> the in-image port.
# <site> site image name for the site variant (e.g. docs, mcp).
# <env-var> runtime environment variable consumed by the service.
#
# Version guard: the tag-triggered service variant fails unless the git tag
# equals the repo-root VERSION file (see the versioning standard). The
# push-triggered site variant ships :latest only and has no version to guard.
---
kind: pipeline
type: docker
@@ -64,6 +68,22 @@ steps:
exit 1
fi
- name: check-version
image: alpine:3.20
commands:
- VERSION=$(cat VERSION 2>/dev/null | tr -d '\n')
- echo "tag=$DRONE_TAG version=$VERSION"
- |
if [ -z "$VERSION" ]; then
echo "VERSION file missing or empty; add it (see versioning standard)."
exit 1
fi
- |
if [ "$DRONE_TAG" != "$VERSION" ]; then
echo "Tag '$DRONE_TAG' does not match VERSION '$VERSION'; refusing to deploy."
exit 1
fi
- name: check-remote-image
image: docker:24
volumes:

View File

@@ -0,0 +1,37 @@
# Changelog
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
<!--
In the release merge request, promote the entries above into a new heading and
leave a fresh, empty [Unreleased] section. Name the version/tag, never a commit
id:
## [0.1.0] - YYYY-MM-DD
### Added
- Initial release.
Then update the compare links below. The tag is created on the merged commit —
see the standard: Versioning & Changelog (registry://release).
-->
[Unreleased]: <repo-url>/compare/0.1.0...HEAD
[0.1.0]: <repo-url>/releases/tag/0.1.0

View File

@@ -0,0 +1 @@
0.0.0

View File

@@ -0,0 +1,119 @@
# Releasing & Versioning
How to cut a release of any Aetos repository. The rule lives in the reference
page [Versioning & Changelog](../lib/11_versioning_and_changelog/); this is the
runbook you follow.
Every repository is one of two Drone shapes — a **PyPI package** or a **Docker
deployment** ([Drone Pipelines](../lib/10_drone_pipelines/)). Both release the
same way: the version bump and changelog promotion go **in the merge request**,
and the tag is created on the merged commit.
---
## 🧾 Before you start
- You have an open **merge request** holding the change, and `main` is green
(the Drone quality gate passed on the last run).
- You know whether this is a **breaking**, **feature**, or **fix** release.
- The version file exists:
- package → `version` in `pyproject.toml`
- service / site → `VERSION` at the repo root
---
## 🚦 Pick the next version
The version file and the tag must be identical. For `0.y.z`, `0.y` is the major
line (see the reference page); once at `1.0.0`, normal SemVer applies.
| Change | `0.y.z` → | `1.0.0+` → |
|--------|-----------|------------|
| Breaking | `0.(y+1).0` | `(x+1).0.0` |
| Feature | `0.y.(z+1)` | `x.(y+1).0` |
| Fix | `0.y.(z+1)` | `x.y.(z+1)` |
---
## 🌿 Release in the MR
The version is known while the change is still a merge request, so the bump
rides along with it. Do both edits **in the MR**:
- **Promote the changelog** in `CHANGELOG.md`: move the `[Unreleased]` content
under a new `## [x.y.z] - YYYY-MM-DD` heading, leave a fresh empty
`[Unreleased]` on top, and add the compare links — naming the **version/tag**,
never a commit id:
```markdown
[Unreleased]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.z...HEAD
[x.y.z]: https://git.aetoskia.com/aetos/<repo>/compare/x.y.(z-1)...x.y.z
```
- **Bump the version file** to the intended release version: `version = "x.y.z"`
in `pyproject.toml` (packages) or the `VERSION` file (services/sites).
---
## 🏷️ Tag the merged commit
1. **Merge or squash** the MR into `main`.
2. **Create the tag** on the resulting commit: `git tag x.y.z` (lightweight).
3. **Push the tag** (`git push origin x.y.z`) — Drone builds the release.
### 🤖 Agent-assisted tagging
The agent does not author or merge the MR and never tags before the merge. After
you confirm the MR is merged/squashed and tell it to proceed, the agent:
1. Waits for that explicit go-ahead.
2. Finds the commit that introduced the bump:
- package:
`git log -S 'version = "x.y.z"' -n 1 --format='%H %s' -- pyproject.toml`
- service / site:
`git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION`
3. Verifies that commit's version file equals `x.y.z`, then tags **only** that
commit: `git tag x.y.z <sha>`.
4. Does **not** push — it asks you to run `git push origin x.y.z`.
---
## 🤖 What Drone does
- **Type A (package):** asserts `tag == pyproject version`, then skips if that
version already exists on the index, runs the quality gate, builds, and
uploads the wheel + sdist.
- **Type B (service):** asserts `tag == VERSION`, resolves the tag, skips if the
image already exists, runs the quality gate, builds, pushes, and restarts the
container.
- **Type B (site):** triggered on push, ships `:latest` and has no tag guard.
If the guard fails, nothing is published — fix the version file, amend the
commit, and move the tag **only** if it was never pushed.
---
## 🛟 Backfill / recovery
If an older tag never published (a past version-file mismatch, an index
outage):
- **Package:** run the `backfill-pypi-from-tags` pipeline (custom event). It
checks out every tag, verifies `tag == pyproject version` at that commit,
and uploads the ones missing from the index. Already-fixed history is never
rewritten.
- **Service:** rebuild the image for the missing tag with a fresh commit and a
new patch version; do not move a published tag.
---
## 🔁 If a release is wrong
- Never reuse or force-move a pushed tag.
- Fix forward: bump to the next patch, promote the changelog, tag again.
---
## Related
- [Testing & Quality Gates](01_testing.md) · [Versioning & Changelog](../lib/11_versioning_and_changelog/) · [Drone Pipelines](../lib/10_drone_pipelines/)

View File

@@ -291,6 +291,25 @@
<li class="md-tabs__item">
<a href="../04_development/02_releasing/" class="md-tabs__link">
Releasing & Versioning
</a>
</li>
</ul>
</div>
</nav>
@@ -523,6 +542,28 @@
<li class="md-nav__item">
<a href="../04_development/02_releasing/" class="md-nav__link">
<span class="md-ellipsis">
Releasing & Versioning
</span>
</a>
</li>
</ul>
</nav>
</div>

View File

@@ -291,6 +291,25 @@
<li class="md-tabs__item">
<a href="../04_development/02_releasing/" class="md-tabs__link">
Releasing & Versioning
</a>
</li>
</ul>
</div>
</nav>
@@ -598,6 +617,28 @@
<li class="md-nav__item">
<a href="../04_development/02_releasing/" class="md-nav__link">
<span class="md-ellipsis">
Releasing & Versioning
</span>
</a>
</li>
</ul>
</nav>
</div>

View File

@@ -291,6 +291,25 @@
<li class="md-tabs__item">
<a href="../04_development/02_releasing/" class="md-tabs__link">
Releasing & Versioning
</a>
</li>
</ul>
</div>
</nav>
@@ -514,6 +533,28 @@
<li class="md-nav__item">
<a href="../04_development/02_releasing/" class="md-nav__link">
<span class="md-ellipsis">
Releasing & Versioning
</span>
</a>
</li>
</ul>
</nav>
</div>

View File

@@ -14,6 +14,8 @@
<link rel="prev" href="../../03_start_an_app/">
<link rel="next" href="../02_releasing/">
<link rel="icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
@@ -289,6 +291,25 @@
<li class="md-tabs__item">
<a href="../02_releasing/" class="md-tabs__link">
Releasing & Versioning
</a>
</li>
</ul>
</div>
</nav>
@@ -539,6 +560,28 @@
<li class="md-nav__item">
<a href="../02_releasing/" class="md-nav__link">
<span class="md-ellipsis">
Releasing & Versioning
</span>
</a>
</li>
</ul>
</nav>
</div>

View File

@@ -0,0 +1,811 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="The philosophy and prompts behind the standardized application blueprint.">
<link rel="prev" href="../01_testing/">
<link rel="icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
<title>Releasing & Versioning - Aetos Standards — Wiki</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../../assets/stylesheets/palette.06af60db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#releasing-versioning" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../.." title="Aetos Standards — Wiki" class="md-header__button md-logo" aria-label="Aetos Standards — Wiki" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Aetos Standards — Wiki
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Releasing & Versioning
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan" aria-hidden="true" type="radio" name="__palette" id="__palette_0">
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../.." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="../../01_overview/" class="md-tabs__link">
Overview
</a>
</li>
<li class="md-tabs__item">
<a href="../../02_components/" class="md-tabs__link">
App Components
</a>
</li>
<li class="md-tabs__item">
<a href="../../03_start_an_app/" class="md-tabs__link">
Start an App
</a>
</li>
<li class="md-tabs__item">
<a href="../01_testing/" class="md-tabs__link">
Testing & Quality
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="./" class="md-tabs__link">
Releasing & Versioning
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted md-nav--integrated" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="Aetos Standards — Wiki" class="md-nav__button md-logo" aria-label="Aetos Standards — Wiki" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Aetos Standards — Wiki
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../01_overview/" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../02_components/" class="md-nav__link">
<span class="md-ellipsis">
App Components
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../03_start_an_app/" class="md-nav__link">
<span class="md-ellipsis">
Start an App
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../01_testing/" class="md-nav__link">
<span class="md-ellipsis">
Testing & Quality
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Releasing & Versioning
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Releasing & Versioning
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#before-you-start" class="md-nav__link">
<span class="md-ellipsis">
🧾 Before you start
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pick-the-next-version" class="md-nav__link">
<span class="md-ellipsis">
🚦 Pick the next version
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#release-in-the-mr" class="md-nav__link">
<span class="md-ellipsis">
🌿 Release in the MR
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#tag-the-merged-commit" class="md-nav__link">
<span class="md-ellipsis">
🏷️ Tag the merged commit
</span>
</a>
<nav class="md-nav" aria-label="🏷️ Tag the merged commit">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#agent-assisted-tagging" class="md-nav__link">
<span class="md-ellipsis">
🤖 Agent-assisted tagging
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#what-drone-does" class="md-nav__link">
<span class="md-ellipsis">
🤖 What Drone does
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#backfill-recovery" class="md-nav__link">
<span class="md-ellipsis">
🛟 Backfill / recovery
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#if-a-release-is-wrong" class="md-nav__link">
<span class="md-ellipsis">
🔁 If a release is wrong
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="releasing-versioning">Releasing &amp; Versioning</h1>
<p>How to cut a release of any Aetos repository. The rule lives in the reference
page <a href="../lib/11_versioning_and_changelog/">Versioning &amp; Changelog</a>; this is the
runbook you follow.</p>
<p>Every repository is one of two Drone shapes — a <strong>PyPI package</strong> or a <strong>Docker
deployment</strong> (<a href="../lib/10_drone_pipelines/">Drone Pipelines</a>). Both release the
same way: the version bump and changelog promotion go <strong>in the merge request</strong>,
and the tag is created on the merged commit.</p>
<hr />
<h2 id="before-you-start">🧾 Before you start</h2>
<ul>
<li>You have an open <strong>merge request</strong> holding the change, and <code>main</code> is green
(the Drone quality gate passed on the last run).</li>
<li>You know whether this is a <strong>breaking</strong>, <strong>feature</strong>, or <strong>fix</strong> release.</li>
<li>The version file exists:</li>
<li>package → <code>version</code> in <code>pyproject.toml</code></li>
<li>service / site → <code>VERSION</code> at the repo root</li>
</ul>
<hr />
<h2 id="pick-the-next-version">🚦 Pick the next version</h2>
<p>The version file and the tag must be identical. For <code>0.y.z</code>, <code>0.y</code> is the major
line (see the reference page); once at <code>1.0.0</code>, normal SemVer applies.</p>
<table>
<thead>
<tr>
<th>Change</th>
<th><code>0.y.z</code> →</th>
<th><code>1.0.0+</code> →</th>
</tr>
</thead>
<tbody>
<tr>
<td>Breaking</td>
<td><code>0.(y+1).0</code></td>
<td><code>(x+1).0.0</code></td>
</tr>
<tr>
<td>Feature</td>
<td><code>0.y.(z+1)</code></td>
<td><code>x.(y+1).0</code></td>
</tr>
<tr>
<td>Fix</td>
<td><code>0.y.(z+1)</code></td>
<td><code>x.y.(z+1)</code></td>
</tr>
</tbody>
</table>
<hr />
<h2 id="release-in-the-mr">🌿 Release in the MR</h2>
<p>The version is known while the change is still a merge request, so the bump
rides along with it. Do both edits <strong>in the MR</strong>:</p>
<ul>
<li><strong>Promote the changelog</strong> in <code>CHANGELOG.md</code>: move the <code>[Unreleased]</code> content
under a new <code>## [x.y.z] - YYYY-MM-DD</code> heading, leave a fresh empty
<code>[Unreleased]</code> on top, and add the compare links — naming the <strong>version/tag</strong>,
never a commit id:</li>
</ul>
<div class="language-markdown highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-0-1">1</a></span>
<span class="normal"><a href="#__codelineno-0-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1"></a>[<span class="nl">Unreleased</span>]: <span class="na">https://git.aetoskia.com/aetos/&lt;repo&gt;/compare/x.y.z...HEAD</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2"></a>[<span class="nl">x.y.z</span>]: <span class="na">https://git.aetoskia.com/aetos/&lt;repo&gt;/compare/x.y.(z-1)...x.y.z</span>
</span></code></pre></div></td></tr></table></div>
<ul>
<li><strong>Bump the version file</strong> to the intended release version: <code>version = "x.y.z"</code>
in <code>pyproject.toml</code> (packages) or the <code>VERSION</code> file (services/sites).</li>
</ul>
<hr />
<h2 id="tag-the-merged-commit">🏷️ Tag the merged commit</h2>
<ol>
<li><strong>Merge or squash</strong> the MR into <code>main</code>.</li>
<li><strong>Create the tag</strong> on the resulting commit: <code>git tag x.y.z</code> (lightweight).</li>
<li><strong>Push the tag</strong> (<code>git push origin x.y.z</code>) — Drone builds the release.</li>
</ol>
<h3 id="agent-assisted-tagging">🤖 Agent-assisted tagging</h3>
<p>The agent does not author or merge the MR and never tags before the merge. After
you confirm the MR is merged/squashed and tell it to proceed, the agent:</p>
<ol>
<li>Waits for that explicit go-ahead.</li>
<li>Finds the commit that introduced the bump:</li>
<li>package:
<code>git log -S 'version = "x.y.z"' -n 1 --format='%H %s' -- pyproject.toml</code></li>
<li>service / site:
<code>git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION</code></li>
<li>Verifies that commit's version file equals <code>x.y.z</code>, then tags <strong>only</strong> that
commit: <code>git tag x.y.z &lt;sha&gt;</code>.</li>
<li>Does <strong>not</strong> push — it asks you to run <code>git push origin x.y.z</code>.</li>
</ol>
<hr />
<h2 id="what-drone-does">🤖 What Drone does</h2>
<ul>
<li><strong>Type A (package):</strong> asserts <code>tag == pyproject version</code>, then skips if that
version already exists on the index, runs the quality gate, builds, and
uploads the wheel + sdist.</li>
<li><strong>Type B (service):</strong> asserts <code>tag == VERSION</code>, resolves the tag, skips if the
image already exists, runs the quality gate, builds, pushes, and restarts the
container.</li>
<li><strong>Type B (site):</strong> triggered on push, ships <code>:latest</code> and has no tag guard.</li>
</ul>
<p>If the guard fails, nothing is published — fix the version file, amend the
commit, and move the tag <strong>only</strong> if it was never pushed.</p>
<hr />
<h2 id="backfill-recovery">🛟 Backfill / recovery</h2>
<p>If an older tag never published (a past version-file mismatch, an index
outage):</p>
<ul>
<li><strong>Package:</strong> run the <code>backfill-pypi-from-tags</code> pipeline (custom event). It
checks out every tag, verifies <code>tag == pyproject version</code> at that commit,
and uploads the ones missing from the index. Already-fixed history is never
rewritten.</li>
<li><strong>Service:</strong> rebuild the image for the missing tag with a fresh commit and a
new patch version; do not move a published tag.</li>
</ul>
<hr />
<h2 id="if-a-release-is-wrong">🔁 If a release is wrong</h2>
<ul>
<li>Never reuse or force-move a pushed tag.</li>
<li>Fix forward: bump to the next patch, promote the changelog, tag again.</li>
</ul>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../01_testing/">Testing &amp; Quality Gates</a> · <a href="../lib/11_versioning_and_changelog/">Versioning &amp; Changelog</a> · <a href="../lib/10_drone_pipelines/">Drone Pipelines</a></li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "../..", "features": ["navigation.sections", "navigation.expand", "navigation.top", "navigation.instant", "navigation.tracking", "navigation.indexes", "navigation.tabs", "toc.integrate", "header.autohide", "content.code.copy", "content.code.annotate", "content.tabs.link", "content.action.edit", "search.highlight", "search.share", "search.suggest"], "search": "../../assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../../assets/javascripts/bundle.f55a23d4.min.js"></script>
<script src="https://unpkg.com/mermaid@10.9.1/dist/mermaid.min.js"></script>
</body>
</html>

View File

@@ -280,6 +280,25 @@
<li class="md-tabs__item">
<a href="/04_development/02_releasing/" class="md-tabs__link">
Releasing & Versioning
</a>
</li>
</ul>
</div>
</nav>
@@ -425,6 +444,28 @@
<li class="md-nav__item">
<a href="/04_development/02_releasing/" class="md-nav__link">
<span class="md-ellipsis">
Releasing & Versioning
</span>
</a>
</li>
</ul>
</nav>
</div>

View File

@@ -289,6 +289,25 @@
<li class="md-tabs__item">
<a href="04_development/02_releasing/" class="md-tabs__link">
Releasing & Versioning
</a>
</li>
</ul>
</div>
</nav>
@@ -503,6 +522,28 @@
<li class="md-nav__item">
<a href="04_development/02_releasing/" class="md-nav__link">
<span class="md-ellipsis">
Releasing & Versioning
</span>
</a>
</li>
</ul>
</nav>
</div>

File diff suppressed because one or more lines are too long

Binary file not shown.