Compare commits
19 Commits
854c23651e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 322741e00c | |||
| da1e46a921 | |||
| 08a9983984 | |||
| e1e5daa30a | |||
| 5a1204fbcb | |||
| 7f951f3fe7 | |||
| a7f0576f1d | |||
| 0857513fce | |||
| b22e5ca432 | |||
| f8cf35de0e | |||
| 0cb35d10a6 | |||
| 56aa1322fb | |||
| 6f6f0d6e6d | |||
| 164aa413da | |||
| d7c344c9aa | |||
| 406affcfcf | |||
| e88005d936 | |||
| ee954431cc | |||
| 86243dbfff |
244
build.py
244
build.py
@@ -194,43 +194,61 @@ def build_bundle(folder: Path, dry_run: bool = False) -> None:
|
||||
|
||||
# ── Template bundle ──────────────────────────────────────────────────────────
|
||||
|
||||
# Component kinds shipped in the vendored template. ``placeholder`` names the
|
||||
# seed token an agent renames: "resource" -> item/Item, "family" -> example/
|
||||
# Example; "" means ship as-is.
|
||||
# Component kinds shipped in the vendored template. The template is the vet
|
||||
# sample: a concrete veterinary-clinic domain (parents, pets, treatments,
|
||||
# vets, appointments + a jobs queue for the worker). Agents copy slices and
|
||||
# adapt them to their own domain — no seed-token renaming. ``placeholder`` is
|
||||
# always "" and kept only for manifest-shape compatibility with the client /
|
||||
# drone / release bundles.
|
||||
_TEMPLATE_COMPONENTS: list[dict[str, str]] = [
|
||||
# Per-resource slice (seed: item/Item).
|
||||
{"kind": "model", "path": "domain/item/models.py", "placeholder": "resource",
|
||||
"description": "Business Pydantic model for a resource."},
|
||||
{"kind": "ports-db", "path": "domain/item/ports/db.py", "placeholder": "resource",
|
||||
"description": "Document model + repository factory for a resource."},
|
||||
{"kind": "ports-service", "path": "domain/item/ports/service.py", "placeholder": "resource",
|
||||
"description": "CRUD service wired through the DI library."},
|
||||
{"kind": "converters", "path": "domain/item/ports/converters.py", "placeholder": "resource",
|
||||
"description": "Optional pure converters between domain types."},
|
||||
{"kind": "routes", "path": "interfaces/item/routes.py", "placeholder": "resource",
|
||||
"description": "Thin HTTP handlers bound to operationIds."},
|
||||
# Per-resource slices — copy one as the pattern for a new aggregate.
|
||||
# parents: simplest slice (no foreign keys).
|
||||
{"kind": "parent-model", "path": "domain/parents/models.py", "placeholder": "",
|
||||
"description": "Example domain model (parents — the simplest slice)."},
|
||||
{"kind": "parent-ports-db", "path": "domain/parents/ports/db.py", "placeholder": "",
|
||||
"description": "Example document model + repository factory."},
|
||||
{"kind": "parent-ports-service", "path": "domain/parents/ports/service.py", "placeholder": "",
|
||||
"description": "Example CRUD service wired through the DI library."},
|
||||
{"kind": "parent-routes", "path": "interfaces/parents/routes.py", "placeholder": "",
|
||||
"description": "Example thin HTTP handlers bound to operationIds."},
|
||||
# pets: embedded list-of-refs + file upload.
|
||||
{"kind": "pet-model", "path": "domain/pets/models.py", "placeholder": "",
|
||||
"description": "Domain model with an embedded parents list."},
|
||||
{"kind": "pet-ports-db", "path": "domain/pets/ports/db.py", "placeholder": "",
|
||||
"description": "Populating document + repository factory (FK wiring)."},
|
||||
{"kind": "pet-routes", "path": "interfaces/pets/routes.py", "placeholder": "",
|
||||
"description": "Routes including the file-upload endpoint."},
|
||||
# appointments: single FKs expanded on read, filtered list.
|
||||
{"kind": "appointment-model", "path": "domain/appointments/models.py", "placeholder": "",
|
||||
"description": "Domain model with single-id FKs + nested value objects."},
|
||||
{"kind": "appointment-routes", "path": "interfaces/appointments/routes.py", "placeholder": "",
|
||||
"description": "Routes with expand_ref FK handling and list filters."},
|
||||
# jobs: the worker's queue (second resource shipped end to end).
|
||||
{"kind": "job-model", "path": "domain/jobs/models.py", "placeholder": "",
|
||||
"description": "Job queue model (the worker's unit of work)."},
|
||||
{"kind": "job-ports-db", "path": "domain/jobs/ports/db.py", "placeholder": "",
|
||||
"description": "Job repository factory."},
|
||||
{"kind": "job-ports-service", "path": "domain/jobs/ports/service.py", "placeholder": "",
|
||||
"description": "Job CRUD service."},
|
||||
{"kind": "job-routes", "path": "interfaces/jobs/routes.py", "placeholder": "",
|
||||
"description": "Job routes (enqueue/inspect via the API)."},
|
||||
# Interface-layer shared files.
|
||||
{"kind": "interface-common", "path": "interfaces/_common.py", "placeholder": "",
|
||||
"description": "Shared helpers: svc resolution, dump, pagination, expand_ref."},
|
||||
{"kind": "openapi-routes", "path": "interfaces/openapi.py", "placeholder": "",
|
||||
"description": "Route registry: flat re-export of every handler namespace."},
|
||||
{"kind": "auth", "path": "interfaces/auth/security.yaml", "placeholder": "",
|
||||
"description": "Mergeable bearerAuth security scheme + global security for openapi.yaml."},
|
||||
{"kind": "test-model", "path": "tests/unit/test_item_models.py", "placeholder": "resource",
|
||||
"description": "Unit test for the resource model."},
|
||||
{"kind": "test-routes", "path": "tests/integration/test_item_routes.py", "placeholder": "resource",
|
||||
"description": "Integration test at the HTTP edge."},
|
||||
{"kind": "test-openapi-build", "path": "tests/integration/test_openapi_app_builds.py", "placeholder": "",
|
||||
"description": "Generic test: every spec operationId resolves and the app builds."},
|
||||
{"kind": "test-pipeline", "path": "tests/unit/test_item_sync.py", "placeholder": "",
|
||||
"description": "Unit test for the smallest pipeline form."},
|
||||
# Per-family pipeline scaffold (seed: example/Example).
|
||||
{"kind": "pipeline-abc", "path": "pipelines/example/abc.py", "placeholder": "family",
|
||||
# Worked pipeline family (appointment_reminder) — the copy-me pattern.
|
||||
{"kind": "pipeline-abc", "path": "pipelines/appointment_reminder/abc.py", "placeholder": "",
|
||||
"description": "Port contracts for a pipeline family."},
|
||||
{"kind": "pipeline-impl", "path": "pipelines/example/impl.py", "placeholder": "family",
|
||||
{"kind": "pipeline-impl", "path": "pipelines/appointment_reminder/impl.py", "placeholder": "",
|
||||
"description": "Concrete stages, wired root, and family registration."},
|
||||
{"kind": "pipeline-yaml", "path": "pipelines/example/pipeline.yaml", "placeholder": "family",
|
||||
{"kind": "pipeline-yaml", "path": "pipelines/appointment_reminder/pipeline.yaml", "placeholder": "",
|
||||
"description": "Graph spec (nodes, edges, roots, schema)."},
|
||||
{"kind": "pipeline-resources", "path": "pipelines/example/resources/pipeline.yaml", "placeholder": "family",
|
||||
{"kind": "pipeline-resources", "path": "pipelines/appointment_reminder/resources/pipeline.yaml", "placeholder": "",
|
||||
"description": "Runtime spec consumed by the shared loader."},
|
||||
{"kind": "pipeline-pattern", "path": "pipelines/example/resources/patterns/example.yaml", "placeholder": "family",
|
||||
{"kind": "pipeline-pattern", "path": "pipelines/appointment_reminder/resources/patterns/reminder.yaml", "placeholder": "",
|
||||
"description": "Pattern/rule fragment merged by the loader."},
|
||||
# Shared pipeline scaffolding (ship as-is).
|
||||
{"kind": "pipeline-ports", "path": "pipelines/shared/ports.py", "placeholder": "",
|
||||
@@ -239,32 +257,23 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [
|
||||
"description": "Shared loader: DI wiring + pattern merge."},
|
||||
{"kind": "pipeline-registry", "path": "pipelines/shared/registry.py", "placeholder": "",
|
||||
"description": "Shared registry: (kind, variant, mode) -> root class."},
|
||||
{"kind": "pipeline-simple", "path": "pipelines/item_sync.py", "placeholder": "",
|
||||
"description": "Smallest pipeline form (plain async steps)."},
|
||||
# Second resource slice (seed: job).
|
||||
{"kind": "job-model", "path": "domain/job/models.py", "placeholder": "",
|
||||
"description": "Seeded second resource model (job)."},
|
||||
{"kind": "job-ports-db", "path": "domain/job/ports/db.py", "placeholder": "",
|
||||
"description": "Seeded second resource repository (job)."},
|
||||
{"kind": "job-ports-service", "path": "domain/job/ports/service.py", "placeholder": "",
|
||||
"description": "Seeded second resource service (job)."},
|
||||
{"kind": "job-routes", "path": "interfaces/job/routes.py", "placeholder": "",
|
||||
"description": "Seeded second resource routes (job)."},
|
||||
# Root / shared files (ship as-is).
|
||||
{"kind": "config", "path": "config.py", "placeholder": "",
|
||||
"description": "Settings dataclass + singleton."},
|
||||
"description": "Settings dataclass + singleton (incl. worker knobs)."},
|
||||
{"kind": "init", "path": "init.py", "placeholder": "",
|
||||
"description": "Composition root (init/shutdown, get_svc)."},
|
||||
"description": "Composition root (init/shutdown, resources dict)."},
|
||||
{"kind": "main", "path": "main.py", "placeholder": "",
|
||||
"description": "Spec-first app bootstrap + lifespan."},
|
||||
{"kind": "worker", "path": "worker.py", "placeholder": "",
|
||||
"description": "Poll loop + job status machine."},
|
||||
"description": "Poll loop + job dispatch by kind + status machine."},
|
||||
{"kind": "openapi", "path": "openapi.yaml", "placeholder": "",
|
||||
"description": "OpenAPI contract skeleton."},
|
||||
"description": "OpenAPI contract (x- extensions drive the admin client)."},
|
||||
{"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": "version-file", "path": "VERSION", "placeholder": "",
|
||||
"description": "Single-line version file (service-docker CI checks tag == VERSION)."},
|
||||
{"kind": "dockerfile", "path": "Dockerfile", "placeholder": "",
|
||||
"description": "API + worker image with private-index wiring."},
|
||||
{"kind": "dockerignore", "path": ".dockerignore", "placeholder": "",
|
||||
@@ -279,12 +288,18 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [
|
||||
"description": "Mongo connection lifecycle."},
|
||||
{"kind": "repo", "path": "infra/db/repos/mongo.py", "placeholder": "",
|
||||
"description": "Repository factory (base/populating/cached)."},
|
||||
{"kind": "repo-int", "path": "infra/db/repos/mongo_int.py", "placeholder": "",
|
||||
"description": "Integer-id repository variants (create_repo factory)."},
|
||||
{"kind": "repo-memory", "path": "infra/db/repos/memory.py", "placeholder": "",
|
||||
"description": "In-memory repository flavour (zero-dependency demo store)."},
|
||||
{"kind": "service-adapter", "path": "infra/adapters/service.py", "placeholder": "",
|
||||
"description": "Generic CRUD service adaptor."},
|
||||
{"kind": "http-cache", "path": "infra/http_cache.py", "placeholder": "",
|
||||
"description": "Response cache decorator + Redis store (generation invalidation)."},
|
||||
{"kind": "progress-bus", "path": "infra/adapters/sse.py", "placeholder": "",
|
||||
"description": "In-memory SSE progress bus (keyed by an event attribute)."},
|
||||
{"kind": "actions-stream", "path": "infra/adapters/actions_stream.py", "placeholder": "",
|
||||
"description": "Per-resource action stream (SSE) adaptor."},
|
||||
{"kind": "kv-store", "path": "infra/adapters/kv.py", "placeholder": "",
|
||||
"description": "KV port + in-memory fallback + optional Redis adaptor."},
|
||||
{"kind": "package-init", "path": "domain/__init__.py", "placeholder": "",
|
||||
@@ -293,6 +308,15 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [
|
||||
"description": "Operator seed script."},
|
||||
{"kind": "script-drop", "path": "scripts/drop.py", "placeholder": "",
|
||||
"description": "Operator drop script."},
|
||||
# Tests shipped with the sample.
|
||||
{"kind": "test-openapi-build", "path": "tests/integration/test_openapi_app_builds.py", "placeholder": "",
|
||||
"description": "Generic test: every spec operationId resolves and the app builds."},
|
||||
{"kind": "test-jobs-routes", "path": "tests/unit/test_jobs_routes.py", "placeholder": "",
|
||||
"description": "Unit test pattern for a resource's routes with service fakes."},
|
||||
{"kind": "test-pipeline", "path": "tests/unit/test_appointment_reminder_pipeline.py", "placeholder": "",
|
||||
"description": "Unit test pattern for a pipeline family."},
|
||||
{"kind": "test-worker", "path": "tests/unit/test_worker.py", "placeholder": "",
|
||||
"description": "Unit test pattern for worker dispatch and status transitions."},
|
||||
]
|
||||
|
||||
|
||||
@@ -303,7 +327,7 @@ def build_components_manifest(template_root: Path) -> dict[str, object]:
|
||||
template_root (Path): The vendored ``templates/app`` directory.
|
||||
|
||||
Returns:
|
||||
dict[str, object]: Manifest with the seed tokens and component entries.
|
||||
dict[str, object]: Manifest with the component entries.
|
||||
"""
|
||||
components: list[dict[str, str]] = []
|
||||
for entry in _TEMPLATE_COMPONENTS:
|
||||
@@ -319,7 +343,7 @@ def build_components_manifest(template_root: Path) -> dict[str, object]:
|
||||
return {
|
||||
"project": "standards",
|
||||
"template": "templates/app",
|
||||
"seed": {"resource": "item", "family": "example"},
|
||||
"sample": "vet",
|
||||
"components": components,
|
||||
}
|
||||
|
||||
@@ -362,14 +386,125 @@ def build_templates(folder: Path, dry_run: bool = False) -> None:
|
||||
f"({len(manifest['components'])} components)")
|
||||
|
||||
|
||||
# ── Client template bundle ────────────────────────────────────────────────────
|
||||
|
||||
# Component kinds shipped in the vendored React client template
|
||||
# (``templates/client``): the Vite + MUI shell that pairs with ``templates/app``
|
||||
# and renders the admin UI from the app's openapi.yaml via
|
||||
# ``@aetoskia/react-openapi`` / ``@aetoskia/react-auth``. No seed tokens —
|
||||
# ship as-is (see standards "40_ui_admin").
|
||||
_CLIENT_COMPONENTS: list[dict[str, str]] = [
|
||||
{"kind": "client-package", "path": "package.json", "placeholder": "",
|
||||
"description": "npm manifest: @aetoskia deps, scripts (dev/build/preview)."},
|
||||
{"kind": "client-npmrc", "path": ".npmrc", "placeholder": "",
|
||||
"description": "Private registry auth pointing at npm.aetoskia.com."},
|
||||
{"kind": "client-env", "path": ".env.example", "placeholder": "",
|
||||
"description": "VITE_API_BASE_URL / VITE_AUTH_BASE_URL seed."},
|
||||
{"kind": "client-vite-config", "path": "vite.config.ts", "placeholder": "",
|
||||
"description": "Vite config (dev server, build)."},
|
||||
{"kind": "client-tsconfig", "path": "tsconfig.json", "placeholder": "",
|
||||
"description": "TypeScript config for the client."},
|
||||
{"kind": "client-index-html", "path": "index.html", "placeholder": "",
|
||||
"description": "HTML entry point."},
|
||||
{"kind": "client-entry", "path": "src/main.jsx", "placeholder": "",
|
||||
"description": "React entry: router + providers + theme."},
|
||||
{"kind": "client-openapi-config", "path": "src/openapi-config.ts", "placeholder": "",
|
||||
"description": "Points the admin renderer at the app spec + auth-server spec."},
|
||||
{"kind": "client-home", "path": "src/Home.tsx", "placeholder": "",
|
||||
"description": "Auto-listing home page (ResourceCard + LiveTable per resource)."},
|
||||
{"kind": "client-header", "path": "src/Header.tsx", "placeholder": "",
|
||||
"description": "App header bar (brand + nav)."},
|
||||
{"kind": "client-footer", "path": "src/Footer.tsx", "placeholder": "",
|
||||
"description": "App footer."},
|
||||
{"kind": "client-vite-env", "path": "src/vite-env.d.ts", "placeholder": "",
|
||||
"description": "Vite client type declarations."},
|
||||
{"kind": "client-theme", "path": "src/shared-theme/AppTheme.tsx", "placeholder": "",
|
||||
"description": "Theme provider wrapper (light/dark)."},
|
||||
{"kind": "client-theme-config", "path": "src/shared-theme/themeConfig.ts", "placeholder": "",
|
||||
"description": "Palette / typography tokens."},
|
||||
{"kind": "client-theme-primitives", "path": "src/shared-theme/themePrimitives.ts", "placeholder": "",
|
||||
"description": "MUI theme primitives (shadows, accents)."},
|
||||
{"kind": "client-theme-shim", "path": "src/shared-theme/mui.d.ts", "placeholder": "",
|
||||
"description": "MUI v5 type shims (Theme.vars, SelectClasses.focused)."},
|
||||
]
|
||||
|
||||
|
||||
def build_client_manifest(template_root: Path) -> dict[str, object]:
|
||||
"""Describe every component in the vendored client template.
|
||||
|
||||
Args:
|
||||
template_root (Path): The vendored ``templates/client`` directory.
|
||||
|
||||
Returns:
|
||||
dict[str, object]: Manifest with the component entries.
|
||||
"""
|
||||
components: list[dict[str, str]] = []
|
||||
for entry in _CLIENT_COMPONENTS:
|
||||
rel = entry["path"]
|
||||
if not (template_root / rel).exists():
|
||||
raise SystemExit(f"[ERR] client component path missing from template: {rel}")
|
||||
components.append({
|
||||
"kind": entry["kind"],
|
||||
"path": rel,
|
||||
"placeholder": entry["placeholder"],
|
||||
"description": entry["description"],
|
||||
})
|
||||
return {
|
||||
"project": "standards",
|
||||
"template": "templates/client",
|
||||
"components": components,
|
||||
}
|
||||
|
||||
|
||||
def build_client_templates(folder: Path, dry_run: bool = False) -> None:
|
||||
"""Vendor ``folder/src/templates/client`` into ``<name>/mcp/templates/client``.
|
||||
|
||||
Ships the React client skeleton (the auto-rendered admin shell) in the MCP
|
||||
bundle so an agent can pull exact, current component code, and emits
|
||||
``client.json`` describing the kinds. Excludes caches and lock files.
|
||||
|
||||
Args:
|
||||
folder (Path): The hub folder owning ``src/templates/client``.
|
||||
dry_run (bool): Print the plan without executing.
|
||||
"""
|
||||
name = folder.name
|
||||
src = folder / "src" / "templates" / "client"
|
||||
if not src.is_dir():
|
||||
return
|
||||
out_dir = folder / "mcp" / "templates" / "client"
|
||||
if dry_run:
|
||||
print(f"[CLT] {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(
|
||||
"node_modules", "dist", ".ruff_cache", ".pytest_cache",
|
||||
".mypy_cache", ".venv", "venv", ".git", "*.egg-info",
|
||||
),
|
||||
)
|
||||
manifest = build_client_manifest(out_dir)
|
||||
(folder / "mcp" / "client.json").write_text(
|
||||
json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8"
|
||||
)
|
||||
print(f"[CLT] {name} client template vendored to {out_dir.relative_to(ROOT)}/ "
|
||||
f"({len(manifest['components'])} components)")
|
||||
|
||||
|
||||
# ── Drone standard bundle ─────────────────────────────────────────────────────
|
||||
|
||||
# The two CI shapes shipped in ``src/templates/drone``. There are exactly two
|
||||
# types of ``.drone.yml`` in the org: a Python package (private PyPI) and a
|
||||
# Docker deployment (build / push / run). See standards "10_drone_pipelines".
|
||||
# The CI shapes shipped in ``src/templates/drone``. There are exactly three
|
||||
# types of ``.drone.yml`` in the org: a Python package (private PyPI), an npm
|
||||
# package (private npm registry), and a Docker deployment (build / push / run).
|
||||
# See standards "90_drone_pipelines".
|
||||
_DRONE_COMPONENTS: list[dict[str, str]] = [
|
||||
{"kind": "drone-pypi", "path": "python-lib.yaml", "placeholder": "",
|
||||
"description": "Drone pipeline: build + publish a Python package to the private PyPI index."},
|
||||
{"kind": "drone-npm", "path": "npm-lib.yaml", "placeholder": "",
|
||||
"description": "Drone pipeline: build + publish an npm package to the private npm registry."},
|
||||
{"kind": "drone-service", "path": "service-docker.yaml", "placeholder": "",
|
||||
"description": "Drone pipeline: build, push, and deploy a Docker service."},
|
||||
]
|
||||
@@ -398,7 +533,7 @@ def build_drone_manifest(template_root: Path) -> dict[str, object]:
|
||||
return {
|
||||
"project": "standards",
|
||||
"template": "templates/drone",
|
||||
"types": ["pypi", "docker"],
|
||||
"types": ["pypi", "npm", "docker"],
|
||||
"components": components,
|
||||
}
|
||||
|
||||
@@ -406,9 +541,9 @@ def build_drone_manifest(template_root: Path) -> dict[str, object]:
|
||||
def build_drone_templates(folder: Path, dry_run: bool = False) -> None:
|
||||
"""Vendor ``folder/src/templates/drone`` into ``<name>/mcp/templates/drone``.
|
||||
|
||||
Ships the two ``.drone.yml`` shapes (PyPI package, Docker deployment) in the
|
||||
MCP bundle so an agent can pull the exact CI file, and emits ``drone.json``
|
||||
describing them.
|
||||
Ships the three ``.drone.yml`` shapes (PyPI package, npm package, Docker
|
||||
deployment) in the MCP bundle so an agent can pull the exact CI file, and
|
||||
emits ``drone.json`` describing them.
|
||||
|
||||
Args:
|
||||
folder (Path): The hub folder owning ``src/templates/drone``.
|
||||
@@ -442,7 +577,7 @@ def build_drone_templates(folder: Path, dry_run: bool = False) -> None:
|
||||
|
||||
# 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".
|
||||
# standards "91_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."},
|
||||
@@ -523,7 +658,7 @@ def build_repos(folder: Path, dry_run: bool = False) -> None:
|
||||
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".
|
||||
"94_repository_inventory".
|
||||
|
||||
Args:
|
||||
folder (Path): The hub folder owning ``src/repos.json``.
|
||||
@@ -566,6 +701,7 @@ def main() -> None:
|
||||
for folder in folders:
|
||||
build_bundle(folder, dry_run=args.dry_run)
|
||||
build_templates(folder, dry_run=args.dry_run)
|
||||
build_client_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)
|
||||
|
||||
@@ -42,7 +42,7 @@ repos:
|
||||
mcp: { bundle: docs/mcp, server: docforge, port: 8001 }
|
||||
|
||||
- name: doc-weaver
|
||||
source: ../doc-weaver
|
||||
source: ../react/doc-weaver
|
||||
title: Doc Weaver
|
||||
description: TypeScript documentation compiler using TypeDoc + JSDoc (RDFC), generating a TypeDoc site and a doc-forge-shaped MCP bundle for React packages.
|
||||
section: libraries
|
||||
|
||||
File diff suppressed because one or more lines are too long
223
registry.py
223
registry.py
@@ -9,7 +9,8 @@ Resources:
|
||||
docs://index, docs://nav, docs://modules/{module} (from the base server)
|
||||
docs://spec (the pinned auth-server OpenAPI contract)
|
||||
registry://libraries (the structured catalog)
|
||||
registry://components (the template component index)
|
||||
registry://components (the app template component index)
|
||||
registry://client (the client template component index)
|
||||
registry://drone (the CI template index)
|
||||
registry://release (the release template index)
|
||||
registry://repos (the repository inventory)
|
||||
@@ -18,9 +19,12 @@ Tools:
|
||||
ping (from the base server)
|
||||
find_library (capability -> matching libraries + MCP endpoints)
|
||||
list_app_components (the template component kinds)
|
||||
get_app_component (kind -> exact template file, seed tokens renamed)
|
||||
get_app_component (kind -> exact template file)
|
||||
get_app_template (the whole app skeleton as a path -> content map)
|
||||
list_drone_templates (the two CI shapes: PyPI package / Docker deployment)
|
||||
list_client_components (the client template component kinds)
|
||||
get_client_component (kind -> exact client template file)
|
||||
get_client_template (the whole client skeleton as a path -> content map)
|
||||
list_drone_templates (the three CI shapes: PyPI package / npm 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)
|
||||
@@ -37,7 +41,6 @@ Prompts:
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -45,11 +48,6 @@ from config import MCPBundle
|
||||
from mcp_server import MCPServer
|
||||
|
||||
|
||||
def _pascal(name: str) -> str:
|
||||
"""Convert a snake/kebab name to PascalCase."""
|
||||
return "".join(part.capitalize() for part in re.split(r"[_\-\s]+", name) if part)
|
||||
|
||||
|
||||
_INTAKE_TEXT = (
|
||||
"# Start a new app — intake\n\n"
|
||||
"Gather the following, then call `build` (or `start_app`) with the values:\n\n"
|
||||
@@ -89,12 +87,17 @@ def _build_text(app_name: str, resources: str, pipelines: str, auth: str) -> str
|
||||
"3. Call `find_library(capability)` for db, di, routing, ingestion, "
|
||||
"and pipeline; never reimplement a Tier A library.\n"
|
||||
"4. Scaffold with `get_app_template()` (whole tree) or pull single "
|
||||
"files with `list_app_components()` and "
|
||||
"`get_app_component(kind, resource=..., family=...)`.\n"
|
||||
"5. If auth is needed, read `docs://spec` (the auth-server contract) and "
|
||||
"files with `list_app_components()` and `get_app_component(kind)` — "
|
||||
"the template is the vet sample; copy a slice and adapt it to your "
|
||||
"domain.\n"
|
||||
"5. If a UI is needed, scaffold the client with `get_client_template()` "
|
||||
"(or pull files via `list_client_components()` / "
|
||||
"`get_client_component(kind)`) — the admin renders from your spec's x- "
|
||||
"extensions; see `docs://modules/ui_admin`.\n"
|
||||
"6. If auth is needed, read `docs://spec` (the auth-server contract) and "
|
||||
"`docs://modules/auth_integration`; declare `securitySchemes.bearerAuth` "
|
||||
"and `security` in your spec — do not write auth code.\n"
|
||||
"6. Author only Tier C; keep Tier B as shipped.\n"
|
||||
"7. Author only Tier C; keep Tier B as shipped.\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -103,8 +106,12 @@ _COMPLIANCE_TEXT = (
|
||||
"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"
|
||||
"- **package** (Python) — `version` in `pyproject.toml`; root `CHANGELOG.md`;\n"
|
||||
" Drone file from `get_drone_template(\"drone-pypi\")` (`python-lib.yaml`,\n"
|
||||
" tag-triggered).\n"
|
||||
"- **package** (npm) — `version` in `package.json`; root `CHANGELOG.md`;\n"
|
||||
" Drone file from `get_drone_template(\"drone-npm\")` (`npm-lib.yaml`,\n"
|
||||
" tag-triggered, publishes to the private npm registry).\n"
|
||||
"- **service** — root `VERSION`; root `CHANGELOG.md`; Drone file from\n"
|
||||
" `get_drone_template(\"drone-service\")` (`service-docker.yaml`, tag-triggered\n"
|
||||
" with a `tag == VERSION` guard). The service must read its version from\n"
|
||||
@@ -267,30 +274,71 @@ class StandardsMCPServer(MCPServer):
|
||||
files[rel] = path.read_text(encoding="utf-8")
|
||||
return files
|
||||
|
||||
def _read_component(
|
||||
self,
|
||||
entry: dict[str, Any],
|
||||
resource: str | None,
|
||||
family: str | None,
|
||||
) -> dict[str, Any]:
|
||||
"""Read a vendored template file, renaming seed tokens when asked."""
|
||||
def _read_component(self, entry: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Read a vendored template file as-is (no token rewriting)."""
|
||||
rel = entry["path"]
|
||||
path = Path(self.mcp_root) / "templates" / "app" / rel
|
||||
if not path.exists():
|
||||
return {"error": "not_found", "path": str(path)}
|
||||
text = path.read_text(encoding="utf-8")
|
||||
placeholder = entry.get("placeholder")
|
||||
if resource and placeholder == "resource":
|
||||
text = text.replace("Item", _pascal(resource)).replace("item", resource)
|
||||
if family and placeholder == "family":
|
||||
text = text.replace("Example", _pascal(family)).replace("example", family)
|
||||
return {
|
||||
"kind": entry["kind"],
|
||||
"path": rel,
|
||||
"placeholder": placeholder,
|
||||
"content": text,
|
||||
"placeholder": entry.get("placeholder"),
|
||||
"content": path.read_text(encoding="utf-8"),
|
||||
}
|
||||
|
||||
def _client(self) -> dict[str, Any]:
|
||||
"""Load the client template component manifest."""
|
||||
path = Path(self.mcp_root) / "client.json"
|
||||
if not path.exists():
|
||||
return {"error": "not_found", "path": str(path), "components": []}
|
||||
return json.loads(path.read_bytes())
|
||||
|
||||
def _find_client_component(self, kind: str) -> dict[str, Any] | None:
|
||||
for entry in self._client().get("components", []):
|
||||
if entry.get("kind") == kind:
|
||||
return entry
|
||||
return None
|
||||
|
||||
def _read_client_component(self, entry: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Read a vendored client template file."""
|
||||
rel = entry["path"]
|
||||
path = Path(self.mcp_root) / "templates" / "client" / 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 _client_files(self, prefix: str | None = None) -> dict[str, str]:
|
||||
"""Read the vendored client template tree as a path -> content map.
|
||||
|
||||
Args:
|
||||
prefix (str | None): Optional path prefix filter with POSIX
|
||||
separators, for example ``"src/shared-theme/"``.
|
||||
|
||||
Returns:
|
||||
dict[str, str]: Relative POSIX path -> UTF-8 file content,
|
||||
excluding caches and build output.
|
||||
"""
|
||||
root = Path(self.mcp_root) / "templates" / "client"
|
||||
files: dict[str, str] = {}
|
||||
if not root.is_dir():
|
||||
return files
|
||||
for path in sorted(root.rglob("*")):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if "__pycache__" in path.parts or path.suffix == ".pyc":
|
||||
continue
|
||||
rel = path.relative_to(root).as_posix()
|
||||
if prefix and not rel.startswith(prefix):
|
||||
continue
|
||||
files[rel] = path.read_text(encoding="utf-8")
|
||||
return files
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Standards surface
|
||||
# ------------------------------------------------------------------
|
||||
@@ -304,6 +352,16 @@ class StandardsMCPServer(MCPServer):
|
||||
def components() -> dict[str, Any]:
|
||||
return self._components()
|
||||
|
||||
@self.app.resource("registry://client")
|
||||
def client() -> dict[str, Any]:
|
||||
"""Return the client template component manifest.
|
||||
|
||||
The React client skeleton (Vite + MUI) that pairs with the app
|
||||
template; the admin UI renders from the app's openapi.yaml x-
|
||||
extensions via @aetoskia/react-openapi.
|
||||
"""
|
||||
return self._client()
|
||||
|
||||
@self.app.resource("registry://drone")
|
||||
def drone() -> dict[str, Any]:
|
||||
return self._drone()
|
||||
@@ -380,22 +438,17 @@ class StandardsMCPServer(MCPServer):
|
||||
return self._components().get("components", [])
|
||||
|
||||
@self.app.tool()
|
||||
def get_app_component(
|
||||
kind: str,
|
||||
resource: str | None = None,
|
||||
family: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
def get_app_component(kind: str) -> dict[str, Any]:
|
||||
"""Return the exact template file for a component kind.
|
||||
|
||||
Use this to copy current, app-neutral skeleton code instead of
|
||||
inventing it. See :func:`list_app_components` for valid kinds.
|
||||
Use this to copy current sample code instead of inventing it. The
|
||||
template is the vet sample (parents/pets/treatments/vets/
|
||||
appointments + jobs + the appointment_reminder pipeline family);
|
||||
copy a slice and adapt it to your domain. See
|
||||
:func:`list_app_components` for valid kinds.
|
||||
|
||||
Args:
|
||||
kind (str): Component kind, e.g. "routes", "pipeline-impl".
|
||||
resource (str | None): Rename the resource seed ("item"/"Item")
|
||||
to this name (snake_case), for per-resource kinds.
|
||||
family (str | None): Rename the family seed ("example"/"Example")
|
||||
to this name (snake_case), for per-family kinds.
|
||||
kind (str): Component kind, e.g. "parent-routes", "pipeline-impl".
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: ``{kind, path, placeholder, content}`` or an
|
||||
@@ -408,7 +461,7 @@ class StandardsMCPServer(MCPServer):
|
||||
"kind": kind,
|
||||
"available": [c["kind"] for c in self._components().get("components", [])],
|
||||
}
|
||||
return self._read_component(entry, resource, family)
|
||||
return self._read_component(entry)
|
||||
|
||||
@self.app.tool()
|
||||
def get_app_template(prefix: str = "") -> dict[str, Any]:
|
||||
@@ -416,8 +469,8 @@ class StandardsMCPServer(MCPServer):
|
||||
|
||||
Use this to scaffold in one shot instead of pulling components one
|
||||
by one; combine with :func:`list_app_components` for the kind and
|
||||
placeholder metadata. Seed tokens (``item`` / ``example``) are left
|
||||
as-is here — fetch a renamed file with :func:`get_app_component`.
|
||||
description metadata. The tree is the vet sample — replace the
|
||||
domain with your own, keeping the layer structure.
|
||||
|
||||
Args:
|
||||
prefix (str): Optional path prefix filter with POSIX
|
||||
@@ -434,13 +487,77 @@ class StandardsMCPServer(MCPServer):
|
||||
"files": files,
|
||||
}
|
||||
|
||||
@self.app.tool()
|
||||
def list_client_components() -> list[dict[str, Any]]:
|
||||
"""List the reusable component kinds shipped in the client template.
|
||||
|
||||
The React client (Vite + MUI) that pairs with the app template:
|
||||
the admin shell rendering the app's resources from its
|
||||
``openapi.yaml``. See :func:`get_client_template` for the whole
|
||||
tree and :func:`list_drone_templates` ("drone-npm") for its CI.
|
||||
|
||||
Returns:
|
||||
list[dict[str, Any]]:
|
||||
Entries with ``kind``, ``path``, ``placeholder`` and
|
||||
``description``. Fetch one with :func:`get_client_component`.
|
||||
"""
|
||||
return self._client().get("components", [])
|
||||
|
||||
@self.app.tool()
|
||||
def get_client_component(kind: str) -> dict[str, Any]:
|
||||
"""Return the exact client template file for a component kind.
|
||||
|
||||
Use this to copy current, app-neutral client code instead of
|
||||
inventing it. See :func:`list_client_components` for valid kinds
|
||||
(e.g. "client-home", "client-header", "client-openapi-config").
|
||||
|
||||
Args:
|
||||
kind (str): Component kind, e.g. "client-openapi-config".
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: ``{kind, path, placeholder, content}`` or an
|
||||
error entry when the kind is unknown.
|
||||
"""
|
||||
entry = self._find_client_component(kind)
|
||||
if entry is None:
|
||||
return {
|
||||
"error": "unknown_kind",
|
||||
"kind": kind,
|
||||
"available": [c["kind"] for c in self._client().get("components", [])],
|
||||
}
|
||||
return self._read_client_component(entry)
|
||||
|
||||
@self.app.tool()
|
||||
def get_client_template(prefix: str = "") -> dict[str, Any]:
|
||||
"""Return the whole client skeleton as a path -> content map.
|
||||
|
||||
Use this to scaffold the UI in one shot instead of pulling
|
||||
components one by one; combine with :func:`list_client_components`
|
||||
for the kind and description metadata.
|
||||
|
||||
Args:
|
||||
prefix (str): Optional path prefix filter with POSIX
|
||||
separators, for example ``"src/shared-theme/"``. Empty
|
||||
returns every file.
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: ``{root, file_count, files: {relpath: content}}``.
|
||||
"""
|
||||
files = self._client_files(prefix or None)
|
||||
return {
|
||||
"root": "templates/client",
|
||||
"file_count": len(files),
|
||||
"files": files,
|
||||
}
|
||||
|
||||
@self.app.tool()
|
||||
def list_drone_templates() -> list[dict[str, Any]]:
|
||||
"""List the CI (Drone) template shapes.
|
||||
|
||||
There are exactly two types of ``.drone.yml`` in the org: a Python
|
||||
package published to the private PyPI index, and a Docker
|
||||
deployment that builds, pushes, and runs a container.
|
||||
There are exactly three types of ``.drone.yml`` in the org: a
|
||||
Python package published to the private PyPI index, an npm package
|
||||
published to the private npm registry, and a Docker deployment
|
||||
that builds, pushes, and runs a container.
|
||||
|
||||
Returns:
|
||||
list[dict[str, Any]]:
|
||||
@@ -455,10 +572,10 @@ class StandardsMCPServer(MCPServer):
|
||||
|
||||
Use this to copy the current ``.drone.yml`` shape instead of
|
||||
inventing one. See :func:`list_drone_templates` for valid kinds
|
||||
("drone-pypi", "drone-service").
|
||||
("drone-pypi", "drone-npm", "drone-service").
|
||||
|
||||
Args:
|
||||
kind (str): Component kind, e.g. "drone-pypi" or
|
||||
kind (str): Component kind, e.g. "drone-pypi", "drone-npm" or
|
||||
"drone-service".
|
||||
|
||||
Returns:
|
||||
@@ -480,7 +597,7 @@ class StandardsMCPServer(MCPServer):
|
||||
|
||||
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".
|
||||
file. See standards "91_versioning_and_changelog".
|
||||
|
||||
Returns:
|
||||
list[dict[str, Any]]:
|
||||
@@ -519,8 +636,8 @@ class StandardsMCPServer(MCPServer):
|
||||
|
||||
Each entry has: ``name``, ``type`` ("package" | "service"),
|
||||
``path`` (relative to the aetos/ workspace root), ``version_source``
|
||||
("pyproject.toml" | "VERSION"), ``drone`` (the template file),
|
||||
``trigger`` ("tag"), ``docs`` kinds, and ``mcp``.
|
||||
("pyproject.toml" | "package.json" | "VERSION"), ``drone`` (the
|
||||
template file), ``trigger`` ("tag"), ``docs`` kinds, and ``mcp``.
|
||||
|
||||
Args:
|
||||
type (str | None): Optional filter, e.g. "package" or "service".
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
<link rel="prev" href="..">
|
||||
|
||||
|
||||
<link rel="next" href="../02_layering_rules/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
|
||||
@@ -374,7 +372,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -396,7 +394,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -418,7 +416,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -440,7 +438,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -462,7 +460,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -484,7 +482,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -506,7 +504,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -528,7 +526,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -550,7 +548,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -572,7 +570,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -594,7 +592,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -616,7 +614,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -638,7 +636,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -660,7 +658,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -785,6 +783,10 @@
|
||||
<p>The ordered, deterministic checklist for generating a new application of the
|
||||
standard shape. Every step is fixed; only the resource model (step 2), the
|
||||
codegen pass (step 3), and the pipeline family (step 8) need app-specific work.</p>
|
||||
<p>The reference pages behind this recipe are organized as an anatomy: the
|
||||
domains layer, the OpenAPI contract that drives routing <em>and</em> the admin UI,
|
||||
the interfaces/infra seam, the auto-rendered UI client, and an optional
|
||||
pipelines layer — see the <a href="../">Standards Reference</a> index.</p>
|
||||
<p>This page is served by the <code>standards</code> MCP as <code>docs://modules/build_recipe</code>. The
|
||||
<code>start_app</code> prompt on that server collects the two axes and hands off here; pull
|
||||
exact files with <code>get_app_component</code>.</p>
|
||||
@@ -795,204 +797,210 @@ exact files with <code>get_app_component</code>.</p>
|
||||
<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>cp<span class="w"> </span>-r<span class="w"> </span><docs>/standards/src/templates/app<span class="w"> </span>apps/<your-app>
|
||||
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2"></a><span class="nb">cd</span><span class="w"> </span>apps/<your-app>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>Pair it with the React client template when a UI is needed (auto-rendered
|
||||
admin from the spec — see <a href="../40_ui_admin/">40 – UI</a>):</p>
|
||||
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-1-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a>cp<span class="w"> </span>-r<span class="w"> </span><docs>/standards/src/templates/client<span class="w"> </span>apps/<your-app>-client
|
||||
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a><span class="nb">cd</span><span class="w"> </span>apps/<your-app>-client<span class="w"> </span><span class="o">&&</span><span class="w"> </span>npm<span class="w"> </span>install
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>Install the internal libraries — editable local checkouts (preferred in
|
||||
development) or the private index (CI / fresh machines). Credentials are
|
||||
environment-only; never commit them.</p>
|
||||
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-1-2">2</a></span>
|
||||
<span class="normal"><a href="#__codelineno-1-3">3</a></span>
|
||||
<span class="normal"><a href="#__codelineno-1-4">4</a></span>
|
||||
<span class="normal"><a href="#__codelineno-1-5">5</a></span>
|
||||
<span class="normal"><a href="#__codelineno-1-6">6</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a><span class="c1"># local checkouts</span>
|
||||
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a>pip<span class="w"> </span>install<span class="w"> </span>-e<span class="w"> </span>../aetos/openapi-first<span class="w"> </span>-e<span class="w"> </span>../aetos/mongo-ops<span class="w"> </span>-e<span class="w"> </span>../aetos/hexa<span class="w"> </span><span class="se">\</span>
|
||||
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3"></a><span class="w"> </span>-e<span class="w"> </span>../aetos/dagpipe<span class="w"> </span>-e<span class="w"> </span>../aetos/doc-forge
|
||||
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4"></a><span class="c1"># private index</span>
|
||||
</span><span id="__span-1-5"><a id="__codelineno-1-5" name="__codelineno-1-5"></a>pip<span class="w"> </span>install<span class="w"> </span>--extra-index-url<span class="w"> </span><span class="se">\</span>
|
||||
</span><span id="__span-1-6"><a id="__codelineno-1-6" name="__codelineno-1-6"></a><span class="w"> </span><span class="s2">"https://</span><span class="nv">$PIP_REGISTRY_USERNAME</span><span class="s2">:</span><span class="nv">$PIP_REGISTRY_PASSWORD</span><span class="s2">@pip.aetoskia.com/simple"</span><span class="w"> </span>.
|
||||
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-2-1">1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-2">2</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-3">3</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-4">4</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-5">5</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-6">6</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a><span class="c1"># local checkouts</span>
|
||||
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a>pip<span class="w"> </span>install<span class="w"> </span>-e<span class="w"> </span>../aetos/openapi-first<span class="w"> </span>-e<span class="w"> </span>../aetos/mongo-ops<span class="w"> </span>-e<span class="w"> </span>../aetos/hexa<span class="w"> </span><span class="se">\</span>
|
||||
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3"></a><span class="w"> </span>-e<span class="w"> </span>../aetos/dagpipe<span class="w"> </span>-e<span class="w"> </span>../aetos/doc-forge
|
||||
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a><span class="c1"># private index</span>
|
||||
</span><span id="__span-2-5"><a id="__codelineno-2-5" name="__codelineno-2-5"></a>pip<span class="w"> </span>install<span class="w"> </span>--extra-index-url<span class="w"> </span><span class="se">\</span>
|
||||
</span><span id="__span-2-6"><a id="__codelineno-2-6" name="__codelineno-2-6"></a><span class="w"> </span><span class="s2">"https://</span><span class="nv">$PIP_REGISTRY_USERNAME</span><span class="s2">:</span><span class="nv">$PIP_REGISTRY_PASSWORD</span><span class="s2">@pip.aetoskia.com/simple"</span><span class="w"> </span>.
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<hr />
|
||||
<h2 id="agent-prompt-copyable">📋 Agent prompt (copyable)</h2>
|
||||
<div class="language-markdown highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-2-1"> 1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-2"> 2</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-3"> 3</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-4"> 4</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-5"> 5</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-6"> 6</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-7"> 7</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-8"> 8</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-9"> 9</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-10">10</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-11">11</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-12">12</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-13">13</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-14">14</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-15">15</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-16">16</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-17">17</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-18">18</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-19">19</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-20">20</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-21">21</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-22">22</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-23">23</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-24">24</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-25">25</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-26">26</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-27">27</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-28">28</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-29">29</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-30">30</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-31">31</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-32">32</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-33">33</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-34">34</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-35">35</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-36">36</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-37">37</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-38">38</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-39">39</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-40">40</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-41">41</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-42">42</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-43">43</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-44">44</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-45">45</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-46">46</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-47">47</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-48">48</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-49">49</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-50">50</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-51">51</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-52">52</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-53">53</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-54">54</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-55">55</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-56">56</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-57">57</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-58">58</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-59">59</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-60">60</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-61">61</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-62">62</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-63">63</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-64">64</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-65">65</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-66">66</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-67">67</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-68">68</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-69">69</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-70">70</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-71">71</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-72">72</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-73">73</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-74">74</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-75">75</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-76">76</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-77">77</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-78">78</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-79">79</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-80">80</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-81">81</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-82">82</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-83">83</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-84">84</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-85">85</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-86">86</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-87">87</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-88">88</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-89">89</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-90">90</a></span>
|
||||
<span class="normal"><a href="#__codelineno-2-91">91</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a><span class="gh"># Build a new application from the Aetos Standards blueprint</span>
|
||||
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a>
|
||||
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3"></a>You are coding a complete new backend application conforming to the Aetos
|
||||
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a>standard app blueprint. Execute every step in order. Only steps 2–4 use the
|
||||
</span><span id="__span-2-5"><a id="__codelineno-2-5" name="__codelineno-2-5"></a>app-specific details provided by the user; everything else is deterministic.
|
||||
</span><span id="__span-2-6"><a id="__codelineno-2-6" name="__codelineno-2-6"></a>
|
||||
</span><span id="__span-2-7"><a id="__codelineno-2-7" name="__codelineno-2-7"></a><span class="gu">## Before writing any code</span>
|
||||
</span><span id="__span-2-8"><a id="__codelineno-2-8" name="__codelineno-2-8"></a>
|
||||
</span><span id="__span-2-9"><a id="__codelineno-2-9" name="__codelineno-2-9"></a>Connect to the <span class="sb">`standards`</span> MCP server and read:
|
||||
</span><span id="__span-2-10"><a id="__codelineno-2-10" name="__codelineno-2-10"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://nav`</span> (the module list)
|
||||
</span><span id="__span-2-11"><a id="__codelineno-2-11" name="__codelineno-2-11"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://modules/genericity_matrix`</span> (Tier A library / Tier B template / Tier C authored)
|
||||
</span><span id="__span-2-12"><a id="__codelineno-2-12" name="__codelineno-2-12"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://modules/layering_rules`</span> (the dependency rules)
|
||||
</span><span id="__span-2-13"><a id="__codelineno-2-13" name="__codelineno-2-13"></a>
|
||||
</span><span id="__span-2-14"><a id="__codelineno-2-14" name="__codelineno-2-14"></a>Then:
|
||||
</span><span id="__span-2-15"><a id="__codelineno-2-15" name="__codelineno-2-15"></a><span class="k">-</span><span class="w"> </span>call <span class="sb">`find_library`</span> with <span class="sb">`db`</span>, <span class="sb">`di`</span>, <span class="sb">`routing`</span>, <span class="sb">`ingestion`</span>, <span class="sb">`pipeline`</span>, <span class="sb">`docs`</span>
|
||||
</span><span id="__span-2-16"><a id="__codelineno-2-16" name="__codelineno-2-16"></a> and note the Tier A libraries that already exist — never reimplement one;
|
||||
</span><span id="__span-2-17"><a id="__codelineno-2-17" name="__codelineno-2-17"></a><span class="k">-</span><span class="w"> </span>call <span class="sb">`list_app_components()`</span> and pull each file you need with
|
||||
</span><span id="__span-2-18"><a id="__codelineno-2-18" name="__codelineno-2-18"></a> <span class="sb">`get_app_component(kind, resource=..., family=...)`</span> instead of writing it from
|
||||
</span><span id="__span-2-19"><a id="__codelineno-2-19" name="__codelineno-2-19"></a> scratch.
|
||||
</span><span id="__span-2-20"><a id="__codelineno-2-20" name="__codelineno-2-20"></a>
|
||||
</span><span id="__span-2-21"><a id="__codelineno-2-21" name="__codelineno-2-21"></a><span class="gu">## Steps</span>
|
||||
</span><span id="__span-2-22"><a id="__codelineno-2-22" name="__codelineno-2-22"></a>
|
||||
</span><span id="__span-2-23"><a id="__codelineno-2-23" name="__codelineno-2-23"></a><span class="k">1.</span> <span class="gs">**Scaffold**</span> — copy <span class="sb">`standards/src/templates/app/`</span> into <span class="sb">`apps/<your-app>/`</span>
|
||||
</span><span id="__span-2-24"><a id="__codelineno-2-24" name="__codelineno-2-24"></a> (or pull the same tree via <span class="sb">`get_app_template`</span>). Keep the tree: <span class="sb">`domain/`</span>,
|
||||
</span><span id="__span-2-25"><a id="__codelineno-2-25" name="__codelineno-2-25"></a> <span class="sb">`infra/`</span>, <span class="sb">`interfaces/`</span>, <span class="sb">`pipelines/`</span>, <span class="sb">`tests/`</span>, <span class="sb">`scripts/`</span>, plus
|
||||
</span><span id="__span-2-26"><a id="__codelineno-2-26" name="__codelineno-2-26"></a> <span class="sb">`pyproject.toml`</span>, <span class="sb">`openapi.yaml`</span>, <span class="sb">`config.py`</span>, <span class="sb">`init.py`</span>, <span class="sb">`main.py`</span>,
|
||||
</span><span id="__span-2-27"><a id="__codelineno-2-27" name="__codelineno-2-27"></a> <span class="sb">`worker.py`</span>, <span class="sb">`Dockerfile`</span>, <span class="sb">`docker-compose.yaml`</span>, <span class="sb">`.env.example`</span>.
|
||||
</span><span id="__span-2-28"><a id="__codelineno-2-28" name="__codelineno-2-28"></a>
|
||||
</span><span id="__span-2-29"><a id="__codelineno-2-29" name="__codelineno-2-29"></a><span class="k">2.</span> <span class="gs">**Author the spec (app-specific)**</span> — write <span class="sb">`openapi.yaml`</span>: resources,
|
||||
</span><span id="__span-2-30"><a id="__codelineno-2-30" name="__codelineno-2-30"></a> schemas, <span class="sb">`operationId`</span>s, security, and extension metadata. This is the
|
||||
</span><span id="__span-2-31"><a id="__codelineno-2-31" name="__codelineno-2-31"></a> contract and drives all routing. Every operation MUST have an <span class="sb">`operationId`</span>.
|
||||
</span><span id="__span-2-32"><a id="__codelineno-2-32" name="__codelineno-2-32"></a>
|
||||
</span><span id="__span-2-33"><a id="__codelineno-2-33" name="__codelineno-2-33"></a><span class="k">3.</span> <span class="gs">**Generate the scaffold (codegen)**</span> — from the spec, generate the wire
|
||||
</span><span id="__span-2-34"><a id="__codelineno-2-34" name="__codelineno-2-34"></a> models and the exact handler signatures:
|
||||
</span><span id="__span-2-35"><a id="__codelineno-2-35" name="__codelineno-2-35"></a><span class="sb"> ```bash</span>
|
||||
</span><span id="__span-2-36"><a id="__codelineno-2-36" name="__codelineno-2-36"></a><span class="w"> </span>openapi-first<span class="w"> </span>models<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>_gen/models.py
|
||||
</span><span id="__span-2-37"><a id="__codelineno-2-37" name="__codelineno-2-37"></a><span class="w"> </span>openapi-first<span class="w"> </span>routes<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>_gen/routes<span class="w"> </span><span class="se">\</span>
|
||||
</span><span id="__span-2-38"><a id="__codelineno-2-38" name="__codelineno-2-38"></a><span class="w"> </span>--use-models<span class="w"> </span>--models-module<span class="w"> </span>_gen.models<span class="w"> </span>--inject-request<span class="w"> </span>--package<span class="w"> </span>--template-style
|
||||
</span><span id="__span-2-39"><a id="__codelineno-2-39" name="__codelineno-2-39"></a><span class="sb"> ```</span>
|
||||
</span><span id="__span-2-40"><a id="__codelineno-2-40" name="__codelineno-2-40"></a> <span class="sb">`_gen/models.py`</span> is the <span class="gs">**wire contract**</span> (Pydantic, one class per
|
||||
</span><span id="__span-2-41"><a id="__codelineno-2-41" name="__codelineno-2-41"></a> <span class="sb">`components.schemas`</span> entry). <span class="sb">`_gen/routes/<resource>/routes.py`</span> lists every
|
||||
</span><span id="__span-2-42"><a id="__codelineno-2-42" name="__codelineno-2-42"></a> <span class="sb">`operationId`</span> with its exact parameter signature. <span class="sb">`--template-style`</span> makes
|
||||
</span><span id="__span-2-43"><a id="__codelineno-2-43" name="__codelineno-2-43"></a> those stubs <span class="sb">`async def`</span> with a <span class="sb">`body`</span> parameter, matching the handlers you
|
||||
</span><span id="__span-2-44"><a id="__codelineno-2-44" name="__codelineno-2-44"></a> keep in <span class="sb">`interfaces/`</span> (without it they are sync <span class="sb">`def`</span> stubs taking
|
||||
</span><span id="__span-2-45"><a id="__codelineno-2-45" name="__codelineno-2-45"></a> <span class="sb">`payload`</span>; both bind the same way). Treat <span class="sb">`_gen/`</span> as generated
|
||||
</span><span id="__span-2-46"><a id="__codelineno-2-46" name="__codelineno-2-46"></a> (never hand-edit it) — the canonical handlers live in <span class="sb">`interfaces/`</span>.
|
||||
</span><span id="__span-2-47"><a id="__codelineno-2-47" name="__codelineno-2-47"></a>
|
||||
</span><span id="__span-2-48"><a id="__codelineno-2-48" name="__codelineno-2-48"></a><span class="k">4.</span> <span class="gs">**Write each CRUD slice (app-specific)**</span> — per resource create the four-file
|
||||
</span><span id="__span-2-49"><a id="__codelineno-2-49" name="__codelineno-2-49"></a> slice: <span class="sb">`models.py`</span> (domain model, <span class="gs">**no `id`**</span>), <span class="sb">`ports/db.py`</span>
|
||||
</span><span id="__span-2-50"><a id="__codelineno-2-50" name="__codelineno-2-50"></a> (<span class="sb">`class XDocument(BaseDocument, X)`</span> — id comes from <span class="sb">`BaseDocument`</span>),
|
||||
</span><span id="__span-2-51"><a id="__codelineno-2-51" name="__codelineno-2-51"></a> <span class="sb">`ports/service.py`</span>, and optional <span class="sb">`converters.py`</span>. Follow the CRUD template.
|
||||
</span><span id="__span-2-52"><a id="__codelineno-2-52" name="__codelineno-2-52"></a>
|
||||
</span><span id="__span-2-53"><a id="__codelineno-2-53" name="__codelineno-2-53"></a><span class="k">5.</span> <span class="gs">**Wire the composition root**</span> — in <span class="sb">`init.py`</span>, build leaf repositories, then
|
||||
</span><span id="__span-2-54"><a id="__codelineno-2-54" name="__codelineno-2-54"></a> dependent repositories, then services; add infra adapters (db, cache, stores,
|
||||
</span><span id="__span-2-55"><a id="__codelineno-2-55" name="__codelineno-2-55"></a> progress bus, fetchers). Expose <span class="sb">`get_svc`</span> overloads.
|
||||
</span><span id="__span-2-56"><a id="__codelineno-2-56" name="__codelineno-2-56"></a>
|
||||
</span><span id="__span-2-57"><a id="__codelineno-2-57" name="__codelineno-2-57"></a><span class="k">6.</span> <span class="gs">**Add routes**</span> — create <span class="sb">`interfaces/<resource>/routes.py`</span> thin handlers whose
|
||||
</span><span id="__span-2-58"><a id="__codelineno-2-58" name="__codelineno-2-58"></a> names match the <span class="sb">`operationId`</span>s (use the signatures from step 3 as your
|
||||
</span><span id="__span-2-59"><a id="__codelineno-2-59" name="__codelineno-2-59"></a> checklist) and re-export them in <span class="sb">`interfaces/openapi.py`</span>. Add the cache
|
||||
</span><span id="__span-2-60"><a id="__codelineno-2-60" name="__codelineno-2-60"></a> decorator to read endpoints.
|
||||
</span><span id="__span-2-61"><a id="__codelineno-2-61" name="__codelineno-2-61"></a>
|
||||
</span><span id="__span-2-62"><a id="__codelineno-2-62" name="__codelineno-2-62"></a><span class="k">7.</span> <span class="gs">**Bootstrap app and worker**</span> — confirm <span class="sb">`main.py`</span> runs the OpenAPI-first app
|
||||
</span><span id="__span-2-63"><a id="__codelineno-2-63" name="__codelineno-2-63"></a> with its lifespan. If the app has long-running jobs, adapt <span class="sb">`worker.py`</span> to poll
|
||||
</span><span id="__span-2-64"><a id="__codelineno-2-64" name="__codelineno-2-64"></a> the job collection, run the pipeline, and publish progress.
|
||||
</span><span id="__span-2-65"><a id="__codelineno-2-65" name="__codelineno-2-65"></a>
|
||||
</span><span id="__span-2-66"><a id="__codelineno-2-66" name="__codelineno-2-66"></a><span class="k">8.</span> <span class="gs">**Add pipeline families (only if data-driven)**</span> — copy <span class="sb">`pipelines/example/`</span>
|
||||
</span><span id="__span-2-67"><a id="__codelineno-2-67" name="__codelineno-2-67"></a> to <span class="sb">`pipelines/<family>/`</span> with <span class="sb">`abc.py`</span>, <span class="sb">`impl.py`</span>, <span class="sb">`pipeline.yaml`</span>,
|
||||
</span><span id="__span-2-68"><a id="__codelineno-2-68" name="__codelineno-2-68"></a> <span class="sb">`resources/`</span>, and reuse <span class="sb">`pipelines/shared/`</span> (loader + registry). Register the
|
||||
</span><span id="__span-2-69"><a id="__codelineno-2-69" name="__codelineno-2-69"></a> root. Add fixtures and a parity test.
|
||||
</span><span id="__span-2-70"><a id="__codelineno-2-70" name="__codelineno-2-70"></a>
|
||||
</span><span id="__span-2-71"><a id="__codelineno-2-71" name="__codelineno-2-71"></a><span class="k">9.</span> <span class="gs">**Add infrastructure only where needed**</span> — Tier A capability: use the
|
||||
</span><span id="__span-2-72"><a id="__codelineno-2-72" name="__codelineno-2-72"></a> library. Tier B capability: copy the template. Never hand-write a Tier A
|
||||
</span><span id="__span-2-73"><a id="__codelineno-2-73" name="__codelineno-2-73"></a> library.
|
||||
</span><span id="__span-2-74"><a id="__codelineno-2-74" name="__codelineno-2-74"></a>
|
||||
</span><span id="__span-2-75"><a id="__codelineno-2-75" name="__codelineno-2-75"></a><span class="k">10.</span> <span class="gs">**Tests**</span> — integration tests with fakes at the HTTP edge; unit tests for
|
||||
</span><span id="__span-2-76"><a id="__codelineno-2-76" name="__codelineno-2-76"></a> domain and pipeline logic; a config-consistency test for rule-driven
|
||||
</span><span id="__span-2-77"><a id="__codelineno-2-77" name="__codelineno-2-77"></a> pipelines. Include <span class="sb">`tests/integration/test_openapi_app_builds.py`</span> (imports
|
||||
</span><span id="__span-2-78"><a id="__codelineno-2-78" name="__codelineno-2-78"></a> the app, asserts <span class="sb">`import main`</span> binds every operation).
|
||||
</span><span id="__span-2-79"><a id="__codelineno-2-79" name="__codelineno-2-79"></a>
|
||||
</span><span id="__span-2-80"><a id="__codelineno-2-80" name="__codelineno-2-80"></a><span class="k">11.</span> <span class="gs">**Scripts and seeds**</span> — operator scripts under <span class="sb">`scripts/`</span> using
|
||||
</span><span id="__span-2-81"><a id="__codelineno-2-81" name="__codelineno-2-81"></a> <span class="sb">`init()`</span> / <span class="sb">`get_svc()`</span> / <span class="sb">`shutdown()`</span>; run as <span class="sb">`python -m scripts.<name>`</span>.
|
||||
</span><span id="__span-2-82"><a id="__codelineno-2-82" name="__codelineno-2-82"></a>
|
||||
</span><span id="__span-2-83"><a id="__codelineno-2-83" name="__codelineno-2-83"></a><span class="k">12.</span> <span class="gs">**Docs**</span> — author GSDFC docstrings, generate <span class="sb">`.pyi`</span> stubs, mark the package
|
||||
</span><span id="__span-2-84"><a id="__codelineno-2-84" name="__codelineno-2-84"></a> <span class="sb">`py.typed`</span>, and build the docs.
|
||||
</span><span id="__span-2-85"><a id="__codelineno-2-85" name="__codelineno-2-85"></a>
|
||||
</span><span id="__span-2-86"><a id="__codelineno-2-86" name="__codelineno-2-86"></a><span class="gs">**Definition of done:**</span>
|
||||
</span><span id="__span-2-87"><a id="__codelineno-2-87" name="__codelineno-2-87"></a><span class="k">-</span><span class="w"> </span><span class="sb">`python -c "import main"`</span> succeeds.
|
||||
</span><span id="__span-2-88"><a id="__codelineno-2-88" name="__codelineno-2-88"></a><span class="k">-</span><span class="w"> </span><span class="sb">`pytest`</span> passes.
|
||||
</span><span id="__span-2-89"><a id="__codelineno-2-89" name="__codelineno-2-89"></a><span class="k">-</span><span class="w"> </span>The app serves every operation in the spec; the worker drains its queue.
|
||||
</span><span id="__span-2-90"><a id="__codelineno-2-90" name="__codelineno-2-90"></a><span class="k">-</span><span class="w"> </span>No domain module imports a web framework.
|
||||
</span><span id="__span-2-91"><a id="__codelineno-2-91" name="__codelineno-2-91"></a><span class="k">-</span><span class="w"> </span>No Tier A library was reimplemented.
|
||||
<div class="language-markdown highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-3-1"> 1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-2"> 2</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-3"> 3</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-4"> 4</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-5"> 5</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-6"> 6</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-7"> 7</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-8"> 8</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-9"> 9</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-10">10</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-11">11</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-12">12</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-13">13</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-14">14</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-15">15</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-16">16</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-17">17</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-18">18</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-19">19</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-20">20</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-21">21</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-22">22</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-23">23</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-24">24</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-25">25</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-26">26</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-27">27</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-28">28</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-29">29</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-30">30</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-31">31</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-32">32</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-33">33</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-34">34</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-35">35</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-36">36</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-37">37</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-38">38</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-39">39</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-40">40</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-41">41</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-42">42</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-43">43</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-44">44</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-45">45</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-46">46</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-47">47</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-48">48</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-49">49</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-50">50</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-51">51</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-52">52</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-53">53</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-54">54</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-55">55</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-56">56</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-57">57</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-58">58</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-59">59</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-60">60</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-61">61</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-62">62</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-63">63</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-64">64</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-65">65</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-66">66</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-67">67</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-68">68</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-69">69</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-70">70</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-71">71</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-72">72</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-73">73</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-74">74</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-75">75</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-76">76</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-77">77</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-78">78</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-79">79</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-80">80</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-81">81</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-82">82</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-83">83</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-84">84</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-85">85</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-86">86</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-87">87</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-88">88</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-89">89</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-90">90</a></span>
|
||||
<span class="normal"><a href="#__codelineno-3-91">91</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1"></a><span class="gh"># Build a new application from the Aetos Standards blueprint</span>
|
||||
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2"></a>
|
||||
</span><span id="__span-3-3"><a id="__codelineno-3-3" name="__codelineno-3-3"></a>You are coding a complete new backend application conforming to the Aetos
|
||||
</span><span id="__span-3-4"><a id="__codelineno-3-4" name="__codelineno-3-4"></a>standard app blueprint. Execute every step in order. Only steps 2–4 use the
|
||||
</span><span id="__span-3-5"><a id="__codelineno-3-5" name="__codelineno-3-5"></a>app-specific details provided by the user; everything else is deterministic.
|
||||
</span><span id="__span-3-6"><a id="__codelineno-3-6" name="__codelineno-3-6"></a>
|
||||
</span><span id="__span-3-7"><a id="__codelineno-3-7" name="__codelineno-3-7"></a><span class="gu">## Before writing any code</span>
|
||||
</span><span id="__span-3-8"><a id="__codelineno-3-8" name="__codelineno-3-8"></a>
|
||||
</span><span id="__span-3-9"><a id="__codelineno-3-9" name="__codelineno-3-9"></a>Connect to the <span class="sb">`standards`</span> MCP server and read:
|
||||
</span><span id="__span-3-10"><a id="__codelineno-3-10" name="__codelineno-3-10"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://nav`</span> (the module list)
|
||||
</span><span id="__span-3-11"><a id="__codelineno-3-11" name="__codelineno-3-11"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://modules/genericity_matrix`</span> (Tier A library / Tier B template / Tier C authored)
|
||||
</span><span id="__span-3-12"><a id="__codelineno-3-12" name="__codelineno-3-12"></a><span class="k">-</span><span class="w"> </span><span class="sb">`docs://modules/layering_rules`</span> (the dependency rules)
|
||||
</span><span id="__span-3-13"><a id="__codelineno-3-13" name="__codelineno-3-13"></a>
|
||||
</span><span id="__span-3-14"><a id="__codelineno-3-14" name="__codelineno-3-14"></a>Then:
|
||||
</span><span id="__span-3-15"><a id="__codelineno-3-15" name="__codelineno-3-15"></a><span class="k">-</span><span class="w"> </span>call <span class="sb">`find_library`</span> with <span class="sb">`db`</span>, <span class="sb">`di`</span>, <span class="sb">`routing`</span>, <span class="sb">`ingestion`</span>, <span class="sb">`pipeline`</span>, <span class="sb">`docs`</span>
|
||||
</span><span id="__span-3-16"><a id="__codelineno-3-16" name="__codelineno-3-16"></a> and note the Tier A libraries that already exist — never reimplement one;
|
||||
</span><span id="__span-3-17"><a id="__codelineno-3-17" name="__codelineno-3-17"></a><span class="k">-</span><span class="w"> </span>call <span class="sb">`list_app_components()`</span> and pull each file you need with
|
||||
</span><span id="__span-3-18"><a id="__codelineno-3-18" name="__codelineno-3-18"></a> <span class="sb">`get_app_component(kind)`</span> instead of writing it from scratch.
|
||||
</span><span id="__span-3-19"><a id="__codelineno-3-19" name="__codelineno-3-19"></a>
|
||||
</span><span id="__span-3-20"><a id="__codelineno-3-20" name="__codelineno-3-20"></a><span class="gu">## Steps</span>
|
||||
</span><span id="__span-3-21"><a id="__codelineno-3-21" name="__codelineno-3-21"></a>
|
||||
</span><span id="__span-3-22"><a id="__codelineno-3-22" name="__codelineno-3-22"></a><span class="k">1.</span> <span class="gs">**Scaffold**</span> — copy <span class="sb">`standards/src/templates/app/`</span> into <span class="sb">`apps/<your-app>/`</span>
|
||||
</span><span id="__span-3-23"><a id="__codelineno-3-23" name="__codelineno-3-23"></a> (or pull the same tree via <span class="sb">`get_app_template`</span>). Keep the tree: <span class="sb">`domain/`</span>,
|
||||
</span><span id="__span-3-24"><a id="__codelineno-3-24" name="__codelineno-3-24"></a> <span class="sb">`infra/`</span>, <span class="sb">`interfaces/`</span>, <span class="sb">`pipelines/`</span>, <span class="sb">`tests/`</span>, <span class="sb">`scripts/`</span>, plus
|
||||
</span><span id="__span-3-25"><a id="__codelineno-3-25" name="__codelineno-3-25"></a> <span class="sb">`pyproject.toml`</span>, <span class="sb">`openapi.yaml`</span>, <span class="sb">`config.py`</span>, <span class="sb">`init.py`</span>, <span class="sb">`main.py`</span>,
|
||||
</span><span id="__span-3-26"><a id="__codelineno-3-26" name="__codelineno-3-26"></a> <span class="sb">`worker.py`</span>, <span class="sb">`Dockerfile`</span>, <span class="sb">`docker-compose.yaml`</span>, <span class="sb">`.env.example`</span>.
|
||||
</span><span id="__span-3-27"><a id="__codelineno-3-27" name="__codelineno-3-27"></a>
|
||||
</span><span id="__span-3-28"><a id="__codelineno-3-28" name="__codelineno-3-28"></a><span class="k">2.</span> <span class="gs">**Author the spec (app-specific)**</span> — write <span class="sb">`openapi.yaml`</span>: resources,
|
||||
</span><span id="__span-3-29"><a id="__codelineno-3-29" name="__codelineno-3-29"></a> schemas, <span class="sb">`operationId`</span>s, security, and extension metadata. This is the
|
||||
</span><span id="__span-3-30"><a id="__codelineno-3-30" name="__codelineno-3-30"></a> contract and drives all routing. Every operation MUST have an <span class="sb">`operationId`</span>.
|
||||
</span><span id="__span-3-31"><a id="__codelineno-3-31" name="__codelineno-3-31"></a>
|
||||
</span><span id="__span-3-32"><a id="__codelineno-3-32" name="__codelineno-3-32"></a><span class="k">3.</span> <span class="gs">**Generate the scaffold (codegen)**</span> — from the spec, generate the wire
|
||||
</span><span id="__span-3-33"><a id="__codelineno-3-33" name="__codelineno-3-33"></a> models and the exact handler signatures:
|
||||
</span><span id="__span-3-34"><a id="__codelineno-3-34" name="__codelineno-3-34"></a><span class="sb"> ```bash</span>
|
||||
</span><span id="__span-3-35"><a id="__codelineno-3-35" name="__codelineno-3-35"></a><span class="w"> </span>openapi-first<span class="w"> </span>models<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>_gen/models.py
|
||||
</span><span id="__span-3-36"><a id="__codelineno-3-36" name="__codelineno-3-36"></a><span class="w"> </span>openapi-first<span class="w"> </span>routes<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>_gen/routes<span class="w"> </span><span class="se">\</span>
|
||||
</span><span id="__span-3-37"><a id="__codelineno-3-37" name="__codelineno-3-37"></a><span class="w"> </span>--use-models<span class="w"> </span>--models-module<span class="w"> </span>_gen.models<span class="w"> </span>--inject-request<span class="w"> </span>--package<span class="w"> </span>--template-style
|
||||
</span><span id="__span-3-38"><a id="__codelineno-3-38" name="__codelineno-3-38"></a><span class="sb"> ```</span>
|
||||
</span><span id="__span-3-39"><a id="__codelineno-3-39" name="__codelineno-3-39"></a> <span class="sb">`_gen/models.py`</span> is the <span class="gs">**wire contract**</span> (Pydantic, one class per
|
||||
</span><span id="__span-3-40"><a id="__codelineno-3-40" name="__codelineno-3-40"></a> <span class="sb">`components.schemas`</span> entry). <span class="sb">`_gen/routes/<resource>/routes.py`</span> lists every
|
||||
</span><span id="__span-3-41"><a id="__codelineno-3-41" name="__codelineno-3-41"></a> <span class="sb">`operationId`</span> with its exact parameter signature. <span class="sb">`--template-style`</span> makes
|
||||
</span><span id="__span-3-42"><a id="__codelineno-3-42" name="__codelineno-3-42"></a> those stubs <span class="sb">`async def`</span> with a <span class="sb">`body`</span> parameter, matching the handlers you
|
||||
</span><span id="__span-3-43"><a id="__codelineno-3-43" name="__codelineno-3-43"></a> keep in <span class="sb">`interfaces/`</span> (without it they are sync <span class="sb">`def`</span> stubs taking
|
||||
</span><span id="__span-3-44"><a id="__codelineno-3-44" name="__codelineno-3-44"></a> <span class="sb">`payload`</span>; both bind the same way). Treat <span class="sb">`_gen/`</span> as generated
|
||||
</span><span id="__span-3-45"><a id="__codelineno-3-45" name="__codelineno-3-45"></a> (never hand-edit it) — the canonical handlers live in <span class="sb">`interfaces/`</span>.
|
||||
</span><span id="__span-3-46"><a id="__codelineno-3-46" name="__codelineno-3-46"></a>
|
||||
</span><span id="__span-3-47"><a id="__codelineno-3-47" name="__codelineno-3-47"></a><span class="k">4.</span> <span class="gs">**Write each CRUD slice (app-specific)**</span> — per resource create the four-file
|
||||
</span><span id="__span-3-48"><a id="__codelineno-3-48" name="__codelineno-3-48"></a> slice: <span class="sb">`models.py`</span> (domain model, <span class="gs">**no `id`**</span>), <span class="sb">`ports/db.py`</span>
|
||||
</span><span id="__span-3-49"><a id="__codelineno-3-49" name="__codelineno-3-49"></a> (<span class="sb">`class XDocument(BaseDocument, X)`</span> — id comes from <span class="sb">`BaseDocument`</span>),
|
||||
</span><span id="__span-3-50"><a id="__codelineno-3-50" name="__codelineno-3-50"></a> <span class="sb">`ports/service.py`</span>, and optional <span class="sb">`converters.py`</span>. Follow the CRUD template.
|
||||
</span><span id="__span-3-51"><a id="__codelineno-3-51" name="__codelineno-3-51"></a>
|
||||
</span><span id="__span-3-52"><a id="__codelineno-3-52" name="__codelineno-3-52"></a><span class="k">5.</span> <span class="gs">**Wire the composition root**</span> — in <span class="sb">`init.py`</span>, build leaf repositories, then
|
||||
</span><span id="__span-3-53"><a id="__codelineno-3-53" name="__codelineno-3-53"></a> dependent repositories, then services; add infra adapters (db, cache, stores,
|
||||
</span><span id="__span-3-54"><a id="__codelineno-3-54" name="__codelineno-3-54"></a> progress bus, fetchers). Expose <span class="sb">`get_svc`</span> overloads.
|
||||
</span><span id="__span-3-55"><a id="__codelineno-3-55" name="__codelineno-3-55"></a>
|
||||
</span><span id="__span-3-56"><a id="__codelineno-3-56" name="__codelineno-3-56"></a><span class="k">6.</span> <span class="gs">**Add routes**</span> — create <span class="sb">`interfaces/<resource>/routes.py`</span> thin handlers whose
|
||||
</span><span id="__span-3-57"><a id="__codelineno-3-57" name="__codelineno-3-57"></a> names match the <span class="sb">`operationId`</span>s (use the signatures from step 3 as your
|
||||
</span><span id="__span-3-58"><a id="__codelineno-3-58" name="__codelineno-3-58"></a> checklist) and re-export them in <span class="sb">`interfaces/openapi.py`</span>. Add the cache
|
||||
</span><span id="__span-3-59"><a id="__codelineno-3-59" name="__codelineno-3-59"></a> decorator to read endpoints.
|
||||
</span><span id="__span-3-60"><a id="__codelineno-3-60" name="__codelineno-3-60"></a>
|
||||
</span><span id="__span-3-61"><a id="__codelineno-3-61" name="__codelineno-3-61"></a><span class="k">7.</span> <span class="gs">**Bootstrap app and worker**</span> — confirm <span class="sb">`main.py`</span> runs the OpenAPI-first app
|
||||
</span><span id="__span-3-62"><a id="__codelineno-3-62" name="__codelineno-3-62"></a> with its lifespan. If the app has long-running jobs, adapt <span class="sb">`worker.py`</span> to poll
|
||||
</span><span id="__span-3-63"><a id="__codelineno-3-63" name="__codelineno-3-63"></a> the job collection, run the pipeline, and publish progress.
|
||||
</span><span id="__span-3-64"><a id="__codelineno-3-64" name="__codelineno-3-64"></a>
|
||||
</span><span id="__span-3-65"><a id="__codelineno-3-65" name="__codelineno-3-65"></a><span class="k">8.</span> <span class="gs">**Add pipeline families (only if data-driven)**</span> — copy the worked family
|
||||
</span><span id="__span-3-66"><a id="__codelineno-3-66" name="__codelineno-3-66"></a> <span class="sb">`pipelines/appointment_reminder/`</span> to <span class="sb">`pipelines/<family>/`</span> with <span class="sb">`abc.py`</span>,
|
||||
</span><span id="__span-3-67"><a id="__codelineno-3-67" name="__codelineno-3-67"></a> <span class="sb">`impl.py`</span>, <span class="sb">`pipeline.yaml`</span>, <span class="sb">`resources/`</span>, and reuse <span class="sb">`pipelines/shared/`</span>
|
||||
</span><span id="__span-3-68"><a id="__codelineno-3-68" name="__codelineno-3-68"></a> (loader + registry). Register the root, add the <span class="sb">`job.kind`</span> handler in
|
||||
</span><span id="__span-3-69"><a id="__codelineno-3-69" name="__codelineno-3-69"></a> <span class="sb">`worker.py`</span>. Add fixtures and a parity test.
|
||||
</span><span id="__span-3-70"><a id="__codelineno-3-70" name="__codelineno-3-70"></a>
|
||||
</span><span id="__span-3-71"><a id="__codelineno-3-71" name="__codelineno-3-71"></a><span class="k">9.</span> <span class="gs">**Add infrastructure only where needed**</span> — Tier A capability: use the
|
||||
</span><span id="__span-3-72"><a id="__codelineno-3-72" name="__codelineno-3-72"></a> library. Tier B capability: copy the template. Never hand-write a Tier A
|
||||
</span><span id="__span-3-73"><a id="__codelineno-3-73" name="__codelineno-3-73"></a> library.
|
||||
</span><span id="__span-3-74"><a id="__codelineno-3-74" name="__codelineno-3-74"></a>
|
||||
</span><span id="__span-3-75"><a id="__codelineno-3-75" name="__codelineno-3-75"></a><span class="k">10.</span> <span class="gs">**Tests**</span> — integration tests with fakes at the HTTP edge; unit tests for
|
||||
</span><span id="__span-3-76"><a id="__codelineno-3-76" name="__codelineno-3-76"></a> domain and pipeline logic; a config-consistency test for rule-driven
|
||||
</span><span id="__span-3-77"><a id="__codelineno-3-77" name="__codelineno-3-77"></a> pipelines. Include <span class="sb">`tests/integration/test_openapi_app_builds.py`</span> (imports
|
||||
</span><span id="__span-3-78"><a id="__codelineno-3-78" name="__codelineno-3-78"></a> the app, asserts <span class="sb">`import main`</span> binds every operation).
|
||||
</span><span id="__span-3-79"><a id="__codelineno-3-79" name="__codelineno-3-79"></a>
|
||||
</span><span id="__span-3-80"><a id="__codelineno-3-80" name="__codelineno-3-80"></a><span class="k">11.</span> <span class="gs">**Scripts and seeds**</span> — operator scripts under <span class="sb">`scripts/`</span> using
|
||||
</span><span id="__span-3-81"><a id="__codelineno-3-81" name="__codelineno-3-81"></a> <span class="sb">`init()`</span> / <span class="sb">`get_svc()`</span> / <span class="sb">`shutdown()`</span>; run as <span class="sb">`python -m scripts.<name>`</span>.
|
||||
</span><span id="__span-3-82"><a id="__codelineno-3-82" name="__codelineno-3-82"></a>
|
||||
</span><span id="__span-3-83"><a id="__codelineno-3-83" name="__codelineno-3-83"></a><span class="k">12.</span> <span class="gs">**Docs**</span> — author GSDFC docstrings, generate <span class="sb">`.pyi`</span> stubs, mark the package
|
||||
</span><span id="__span-3-84"><a id="__codelineno-3-84" name="__codelineno-3-84"></a> <span class="sb">`py.typed`</span>, and build the docs.
|
||||
</span><span id="__span-3-85"><a id="__codelineno-3-85" name="__codelineno-3-85"></a>
|
||||
</span><span id="__span-3-86"><a id="__codelineno-3-86" name="__codelineno-3-86"></a><span class="gs">**Definition of done:**</span>
|
||||
</span><span id="__span-3-87"><a id="__codelineno-3-87" name="__codelineno-3-87"></a><span class="k">-</span><span class="w"> </span><span class="sb">`python -c "import main"`</span> succeeds.
|
||||
</span><span id="__span-3-88"><a id="__codelineno-3-88" name="__codelineno-3-88"></a><span class="k">-</span><span class="w"> </span><span class="sb">`pytest`</span> passes.
|
||||
</span><span id="__span-3-89"><a id="__codelineno-3-89" name="__codelineno-3-89"></a><span class="k">-</span><span class="w"> </span>The app serves every operation in the spec; the worker drains its queue.
|
||||
</span><span id="__span-3-90"><a id="__codelineno-3-90" name="__codelineno-3-90"></a><span class="k">-</span><span class="w"> </span>No domain module imports a web framework.
|
||||
</span><span id="__span-3-91"><a id="__codelineno-3-91" name="__codelineno-3-91"></a><span class="k">-</span><span class="w"> </span>No Tier A library was reimplemented.
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<hr />
|
||||
<h2 id="where-the-steps-land">🧭 Where the steps land</h2>
|
||||
@@ -1120,7 +1128,7 @@ the two mechanical parts of the build:</p>
|
||||
<hr />
|
||||
<h2 id="related">Related</h2>
|
||||
<ul>
|
||||
<li><a href="../02_layering_rules/">02 – Layering Rules</a> · <a href="../05_crud_template/">05 – CRUD Template</a> · <a href="../06_pipeline_scaffold/">06 – Pipeline Scaffold</a> · <a href="../12_genericity_matrix/">12 – Genericity Matrix</a></li>
|
||||
<li><a href="../10_layering_rules/">10 – Layering Rules</a> · <a href="../21_crud_template/">21 – CRUD Template</a> · <a href="../22_auth_integration/">22 – Auth Integration</a> · <a href="../40_ui_admin/">40 – UI</a> · <a href="../50_pipeline_scaffold/">50 – Pipeline Scaffold</a> · <a href="../92_genericity_matrix/">92 – Genericity Matrix</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../02_layering_rules/">
|
||||
|
||||
|
||||
<link rel="next" href="../04_spec_first_routing/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -288,32 +284,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Composition Root
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -324,71 +298,6 @@
|
||||
|
||||
|
||||
</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="#the-resource-registry" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The resource registry
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#shape" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Shape
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#typed-accessors" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Typed accessors
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#rules" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Rules
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -400,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -422,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -444,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -466,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -488,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -510,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -532,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -554,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -576,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -598,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -620,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -642,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../01_build_recipe/">
|
||||
|
||||
|
||||
<link rel="next" href="../03_composition_root/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -266,32 +262,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Layering Rules
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -302,80 +276,6 @@
|
||||
|
||||
|
||||
</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="#allowed-imports" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Allowed imports
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#forbidden-imports" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Forbidden imports
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#why" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Why
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#the-portadapter-contract" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The port/adapter contract
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#enforcement" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Enforcement
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -387,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -409,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -431,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -453,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -475,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -497,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -519,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -541,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -563,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -585,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -607,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -629,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -651,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../03_composition_root/">
|
||||
|
||||
|
||||
<link rel="next" href="../05_crud_template/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -310,32 +306,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Spec-First Routing
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -346,98 +320,6 @@
|
||||
|
||||
|
||||
</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="#the-contract" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The contract
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#app-bootstrap" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
App bootstrap
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#handler-registry" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Handler registry
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#handler-shape" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Handler shape
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#conventions" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Conventions
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#authless-apps" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Authless apps
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#codegen" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Codegen
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -449,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -471,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -493,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -515,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -537,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -559,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -581,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -603,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -625,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -647,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -669,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../04_spec_first_routing/">
|
||||
|
||||
|
||||
<link rel="next" href="../06_pipeline_scaffold/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -332,32 +328,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
CRUD Template
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -368,107 +342,6 @@
|
||||
|
||||
|
||||
</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="#the-slice" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The slice
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#the-id-rule-model-vs-document" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The id rule (model vs. document)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#modelspy" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
models.py
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#portsdbpy" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
ports/db.py
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#nested-references" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Nested references
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#portsservicepy" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
ports/service.py
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#naming-conventions" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Naming conventions
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#optional-files" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Optional files
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -480,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -502,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -524,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -546,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -568,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -590,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -612,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -634,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -656,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -678,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../12_genericity_matrix/">
|
||||
|
||||
|
||||
<link rel="next" href="../13_library_registry/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -357,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -379,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -401,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -423,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -445,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -467,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -489,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -511,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -530,32 +526,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Auth Integration
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -566,104 +540,6 @@
|
||||
|
||||
|
||||
</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="#read-the-contract" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Read the contract
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#wire-it-up-declare-dont-code" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Wire it up: declare, don't code
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="Wire it up: declare, don't code">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#public-operations" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Public operations
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#environment" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Environment
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#path-1-jwtlib-client-no-generator" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Path 1: jwtlib client (no generator)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#golden-rules" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Golden rules
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#notes" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Notes
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -675,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../08_human_in_loop/">
|
||||
|
||||
|
||||
<link rel="next" href="../10_drone_pipelines/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -357,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -379,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -401,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -420,32 +416,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Infra Adapters
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -456,98 +430,6 @@
|
||||
|
||||
|
||||
</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="#database-mongodb" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Database (MongoDB)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#http-response-cache" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
HTTP response cache
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#event-progress-bus" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Event / progress bus
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#external-clients-ingestion" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
External clients (ingestion)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#llm-clients" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
LLM clients
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#key-value-store" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Key-value store
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#rule" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Rule
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -559,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -581,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -603,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -625,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -647,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -669,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/02_layering_rules/" class="md-nav__link">
|
||||
<a href="/02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/03_composition_root/" class="md-nav__link">
|
||||
<a href="/03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="/04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/05_crud_template/" class="md-nav__link">
|
||||
<a href="/05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="/06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="/07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -392,7 +392,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/08_human_in_loop/" class="md-nav__link">
|
||||
<a href="/08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -414,7 +414,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="/09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -436,7 +436,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="/10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -458,7 +458,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="/11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -480,7 +480,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/15_repository_inventory/" class="md-nav__link">
|
||||
<a href="/15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -502,7 +502,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="/12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -524,7 +524,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/14_auth_integration/" class="md-nav__link">
|
||||
<a href="/14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -546,7 +546,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/13_library_registry/" class="md-nav__link">
|
||||
<a href="/13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
|
||||
806
standards/lib/40_ui_admin/index.html
Normal file
806
standards/lib/40_ui_admin/index.html
Normal file
@@ -0,0 +1,806 @@
|
||||
|
||||
<!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="icon" href="../assets/images/favicon.png">
|
||||
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
|
||||
|
||||
|
||||
|
||||
<title>UI: Auto-Rendered Admin & Custom Pages - 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="#ui-auto-rendered-admin-custom-pages" 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">
|
||||
|
||||
UI: Auto-Rendered Admin & Custom Pages
|
||||
|
||||
</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.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Layering Rules
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Composition Root
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Spec-First Routing
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
CRUD Template
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Pipeline Scaffold
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Worker & Progress
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop.md" 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.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Infra Adapters
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Drone Pipelines
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Versioning & Changelog
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Repository Inventory
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Genericity Matrix
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Auth Integration
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry.md" 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="#scaffold" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Scaffold
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#x-extensions-how-the-spec-steers-the-ui" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
x- extensions: how the spec steers the UI
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#golden-rules" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Golden rules
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#notes" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Notes
|
||||
</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="ui-auto-rendered-admin-custom-pages">UI: Auto-Rendered Admin & Custom Pages</h1>
|
||||
<h2 id="summary">Summary</h2>
|
||||
<p>The UI axis of an Aetos app is a <strong>published React client</strong>, not code written
|
||||
per application. Two npm packages carry the whole surface:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Package</th>
|
||||
<th>Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>@aetoskia/react-openapi</code></td>
|
||||
<td>Renders a complete admin panel (CRUD tables, forms, filters, sorting, pagination, uploads) <strong>from the app's <code>openapi.yaml</code></strong> — zero UI code.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@aetoskia/react-auth</code></td>
|
||||
<td>Auth contexts and pages (login/register/me) wired to the auth-server, plus the axios client with token handling.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Both publish to the private registry <code>https://npm.aetoskia.com</code>. The server
|
||||
spec is the single source of truth: the same <code>openapi.yaml</code> that drives
|
||||
<code>openapi-first</code> routing drives the admin UI, through <strong>x- extension fields</strong>
|
||||
(see below).</p>
|
||||
<hr />
|
||||
<h2 id="scaffold">Scaffold</h2>
|
||||
<div class="language-bash 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>
|
||||
<span class="normal"><a href="#__codelineno-0-3">3</a></span>
|
||||
<span class="normal"><a href="#__codelineno-0-4">4</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>cp<span class="w"> </span>-r<span class="w"> </span><docs>/standards/src/templates/client<span class="w"> </span>apps/<your-app>-client
|
||||
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2"></a><span class="nb">cd</span><span class="w"> </span>apps/<your-app>-client
|
||||
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3"></a>npm<span class="w"> </span>install<span class="w"> </span><span class="c1"># deps come from npm.aetoskia.com via .npmrc</span>
|
||||
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4"></a>npm<span class="w"> </span>run<span class="w"> </span>dev
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>The client template is a Vite + MUI app with:</p>
|
||||
<ul>
|
||||
<li><code>src/openapi-config.ts</code> — points the renderer at the app's spec and the
|
||||
auth-server's spec;</li>
|
||||
<li><code>.env.example</code> — <code>VITE_API_BASE_URL</code> (app API) and <code>VITE_AUTH_BASE_URL</code>
|
||||
(auth-server);</li>
|
||||
<li>a <code>Home</code> page that auto-lists every resource the spec declares, and a
|
||||
<code>Header</code>/<code>Footer</code>/theme scaffold.</li>
|
||||
</ul>
|
||||
<p>Custom pages are ordinary React routes layered on top; the auto-rendered admin
|
||||
is available per resource at <code>#/resource/<name></code>.</p>
|
||||
<hr />
|
||||
<h2 id="x-extensions-how-the-spec-steers-the-ui">x- extensions: how the spec steers the UI</h2>
|
||||
<p>The admin renderer reads a catalog of OpenAPI extension fields (<code>x-resource</code>,
|
||||
<code>x-list-columns</code>, <code>x-display-format</code>, <code>x-label</code>, <code>x-order</code>, <code>x-hidden</code>,
|
||||
<code>x-sortable</code>, <code>x-filterable</code>, <code>x-autocomplete</code>, <code>x-fk</code>, <code>x-ui-type</code>,
|
||||
<code>x-upload</code>, <code>x-sse</code>, <code>x-profile</code>, and the auth wiring <code>x-server-url</code> /
|
||||
<code>x-login-path</code> / <code>x-me-path</code> / …) declared on schemas and properties.</p>
|
||||
<p><strong>The catalog is canonical in the react-openapi source, not in this doc.</strong>
|
||||
Read it from the MCP bundle:</p>
|
||||
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a>docs://modules/react-openapi # module: specExtensions — the full catalog
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>The app's own spec carries the per-resource values; the catalog explains what
|
||||
each field does. The template's <code>openapi.yaml</code> shows the convention on the
|
||||
example <code>Job</code> schema.</p>
|
||||
<hr />
|
||||
<h2 id="golden-rules">Golden rules</h2>
|
||||
<ol>
|
||||
<li><strong>The spec owns the UI.</strong> Add a resource or rename a column in
|
||||
<code>openapi.yaml</code>; the admin follows. Never fork the renderer per app.</li>
|
||||
<li><strong>Custom pages for workflows, auto-render for CRUD.</strong> Anything that is
|
||||
"list / create / edit / delete an entity" is a spec concern; anything that
|
||||
is a bespoke workflow is a React page.</li>
|
||||
<li><strong>Auth is consumed, never re-implemented.</strong> <code>@aetoskia/react-auth</code> holds
|
||||
tokens and calls the auth-server endpoints declared by the x- wiring; pages
|
||||
read the context.</li>
|
||||
<li><strong>Publish, don't vendor.</strong> UI shared across apps belongs in
|
||||
<code>@aetoskia/react-openapi</code> or <code>@aetoskia/react-auth</code> (SemVer, tag-triggered
|
||||
npm release), not copy-pasted into app trees.</li>
|
||||
</ol>
|
||||
<hr />
|
||||
<h2 id="notes">Notes</h2>
|
||||
<ul>
|
||||
<li>The server template ships authless by default (see
|
||||
<a href="../22_auth_integration/">22 – Auth Integration</a>); the client template pairs
|
||||
with it and degrades to "no login" until a security scheme is declared.</li>
|
||||
<li>The standards <strong>app + client templates</strong> are the full-stack reference: the
|
||||
server spec carries the x- extensions, and the client renders the admin from
|
||||
it. The wiki's <strong>Reference App</strong> walkthrough (<code>06_reference_app</code>) documents
|
||||
the pairing end to end.</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>
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../05_crud_template/">
|
||||
|
||||
|
||||
<link rel="next" href="../07_worker_and_progress/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -354,32 +350,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Pipeline Scaffold
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -390,98 +364,6 @@
|
||||
|
||||
|
||||
</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="#when-to-use-a-pipeline" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
When to use a pipeline
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#structure-of-a-family" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Structure of a family
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#shared-scaffolding" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Shared scaffolding
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#the-loader" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The loader
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#the-registry" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The registry
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#driving-a-pipeline" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Driving a pipeline
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#testing" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Testing
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -493,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -515,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -537,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -559,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -581,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -603,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -625,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -647,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -669,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../06_pipeline_scaffold/">
|
||||
|
||||
|
||||
<link rel="next" href="../08_human_in_loop/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -110,7 +106,7 @@
|
||||
<div class="md-header__topic" data-md-component="header-topic">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Worker & Progress
|
||||
Worker & Progress
|
||||
|
||||
</span>
|
||||
</div>
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -357,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -376,32 +372,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Worker & Progress
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -412,71 +386,6 @@
|
||||
|
||||
|
||||
</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="#the-status-machine" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The status machine
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#the-poll-loop" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The poll loop
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#progress-events" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Progress events
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#streaming-endpoint" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Streaming endpoint
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -488,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -510,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -532,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -554,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -576,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -598,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -620,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -642,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../07_worker_and_progress/">
|
||||
|
||||
|
||||
<link rel="next" href="../09_infra_adaptors/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -357,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -379,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -398,32 +394,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Human-in-the-Loop
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -434,80 +408,6 @@
|
||||
|
||||
|
||||
</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="#the-mechanism" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The mechanism
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#the-question-document" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The question document
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#resume-map" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Resume map
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#resolve-endpoint" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Resolve endpoint
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#cli-parity" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
CLI parity
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -519,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -541,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -563,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -585,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -607,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -629,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -651,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../09_infra_adaptors/">
|
||||
|
||||
|
||||
<link rel="next" href="../11_versioning_and_changelog/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -357,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -379,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -401,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -423,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -442,32 +438,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -478,71 +452,6 @@
|
||||
|
||||
|
||||
</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>
|
||||
|
||||
|
||||
@@ -554,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -576,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -598,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -620,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -642,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -702,9 +611,18 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#type-b-docker-deployment-service-dockeryaml" class="md-nav__link">
|
||||
<a href="#type-b-npm-package-npm-libyaml" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Type B — Docker deployment (service-docker.yaml)
|
||||
Type B — npm package (npm-lib.yaml)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#type-c-docker-deployment-service-dockeryaml" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Type C — Docker deployment (service-docker.yaml)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@@ -747,7 +665,7 @@
|
||||
<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>,
|
||||
exactly <strong>one standard per repository</strong>, and there are <strong>three types of file</strong>,
|
||||
chosen by what the repository produces:</p>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -766,29 +684,36 @@ chosen by what the repository produces:</p>
|
||||
<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><strong>B — npm package</strong></td>
|
||||
<td><code>templates/drone/npm-lib.yaml</code></td>
|
||||
<td>A package published to the private npm registry</td>
|
||||
<td><code>doc-weaver</code>, <code>react-auth</code>, <code>react-openapi</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>C — 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></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><repo>/.drone.yml</code>; the Type A file
|
||||
needs no edits, the Type B file needs its placeholders filled in (below).</p>
|
||||
<p>All templates run on <code>platform: linux / arm64</code> with the workspace at
|
||||
<code>/drone/src</code>. Copy the matching template to <code><repo>/.drone.yml</code>; the Type A and
|
||||
Type B files need no edits, the Type C 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>
|
||||
<p>For internal Python 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/<package>/</code>; if that version already exists, exit
|
||||
<code>78</code> and skip the whole build (see <a href="../11_versioning_and_changelog/">11 – Versioning & Changelog</a>).</li>
|
||||
<code>78</code> and skip the whole build (see <a href="../91_versioning_and_changelog/">91 – Versioning & 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>
|
||||
@@ -807,7 +732,50 @@ with <code>from_secret</code> — never commit credentials.</p>
|
||||
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>
|
||||
<h2 id="type-b-npm-package-npm-libyaml">Type B — npm package (<code>npm-lib.yaml</code>)</h2>
|
||||
<p>For internal npm libraries (the React packages). The file contains <strong>two
|
||||
pipelines</strong>:</p>
|
||||
<ol>
|
||||
<li><strong><code>build-and-publish-npm</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>package.json</code>; query <code>$NPM_REPO_URL</code> for <code>name@version</code>; if it already
|
||||
exists, exit <code>78</code> and skip the whole build.</li>
|
||||
<li><code>quality-gate</code> — write <code>.npmrc</code> from the secrets, <code>npm install</code>, then
|
||||
<code>npm run quality-gate</code>.</li>
|
||||
<li><code>build-package</code> — <code>npm install && npm run build</code> into <code>dist/</code>.</li>
|
||||
<li><code>publish-to-private-npm</code> — <code>npm publish --access restricted</code> to
|
||||
<code>$NPM_REPO_URL</code>.</li>
|
||||
<li><strong><code>backfill-npm-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-publish-missing</code> — walk every tag in version order, skip the
|
||||
ones already on the registry, <code>git checkout</code> the rest, verify
|
||||
<code>tag == package.json version</code> at that commit, build, and publish. This is
|
||||
the disaster-recovery / replay path.</li>
|
||||
</ol>
|
||||
<p>Secrets: <code>NPM_REPO_URL</code>, <code>NPM_USERNAME</code>, <code>NPM_PASSWORD</code>. Auth is a generated
|
||||
<code>.npmrc</code> written from secrets at runtime. It sets a <strong>scope-scoped</strong> registry,
|
||||
never a global one — <code>@aetoskia:registry=$NPM_REPO_URL</code> — plus a host-scoped
|
||||
auth line <code>//<host>/:_auth=<base64(user:pass)></code>. The scope and host are derived
|
||||
from <code>package.json</code> and <code>NPM_REPO_URL</code> respectively. A global <code>registry=</code> would
|
||||
push public transitive deps through Verdaccio (which has no uplink) and 404
|
||||
<code>npm install</code>; a bare <code>_auth=</code> is rejected by modern npm. See
|
||||
<a href="../93_library_registry/">93 – Library Registry</a>.</p>
|
||||
<ul>
|
||||
<li><strong>Ship as-is.</strong> Name and version come from <code>package.json</code>, so the same file
|
||||
is reused verbatim across every npm package.</li>
|
||||
</ul>
|
||||
<blockquote>
|
||||
<p><strong>Registry credentials are shared.</strong> The private npm registry (Verdaccio at
|
||||
<code>npm.aetoskia.com</code>) authenticates against the <strong>same <code>htpasswd</code> file</strong> as the
|
||||
private PyPI index and the Docker registry (<code>/auth/htpasswd</code>), with
|
||||
registration disabled (<code>max_users: -1</code>). <code>NPM_USERNAME</code> / <code>NPM_PASSWORD</code> are
|
||||
therefore the <strong>same credentials as <code>PIP_USERNAME</code> / <code>PIP_PASSWORD</code></strong> — no
|
||||
separate npm account exists. Verdaccio's htpasswd plugin accepts <strong>bcrypt</strong>
|
||||
hashes (<code>$2y$…</code>); non-bcrypt entries that pypiserver tolerates will not
|
||||
authenticate against npm. See <a href="../93_library_registry/">93 – Library Registry</a>.</p>
|
||||
</blockquote>
|
||||
<hr />
|
||||
<h2 id="type-c-docker-deployment-service-dockeryaml">Type C — Docker deployment (<code>service-docker.yaml</code>)</h2>
|
||||
<p>For a service that ships a container image — a long-running API/worker, e.g.
|
||||
<code>auth-server</code>. The file contains <strong>one pipeline</strong>, <code>build-and-deploy-service</code>,
|
||||
triggered on <code>tag</code>:</p>
|
||||
@@ -815,7 +783,7 @@ triggered on <code>tag</code>:</p>
|
||||
<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 & Changelog</a>).</li>
|
||||
<code>VERSION</code> file (see <a href="../91_versioning_and_changelog/">91 – Versioning & Changelog</a>).</li>
|
||||
<li><code>check-remote-image</code> — if <code>aetos/<app>:<tag></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
|
||||
@@ -865,20 +833,23 @@ triggered on <code>tag</code>:</p>
|
||||
<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 hand-roll a fourth shape. A repo is exactly one of Type A, B, or C,
|
||||
never combinations.</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 & Changelog</a>.</li>
|
||||
<li>Do not drop the tag↔version guard. A Python package needs its
|
||||
<code>pyproject.toml</code> version to equal the tag; an npm package needs its
|
||||
<code>package.json</code> version; a service needs a root <code>VERSION</code> file. See
|
||||
<a href="../91_versioning_and_changelog/">91 – Versioning & Changelog</a>.</li>
|
||||
<li>Do not rename the pipelines; tooling and runbooks key off
|
||||
<code>build-and-publish-pypi</code> and <code>build-and-deploy-service</code>.</li>
|
||||
<code>build-and-publish-pypi</code>, <code>build-and-publish-npm</code>,
|
||||
<code>build-and-deploy-service</code>, and their <code>backfill-*-from-tags</code> companions.</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 & Changelog</a> · <a href="../13_library_registry/">13 – Library Registry</a></li>
|
||||
<li><a href="../01_build_recipe/">01 – Build Recipe</a> · <a href="../30_infra_adaptors/">30 – Infra Adapters</a> · <a href="../91_versioning_and_changelog/">91 – Versioning & Changelog</a> · <a href="../93_library_registry/">93 – Library Registry</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../10_drone_pipelines/">
|
||||
|
||||
|
||||
<link rel="next" href="../15_repository_inventory/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -110,7 +106,7 @@
|
||||
<div class="md-header__topic" data-md-component="header-topic">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Versioning & Changelog
|
||||
Versioning & Changelog
|
||||
|
||||
</span>
|
||||
</div>
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -357,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -379,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -401,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -423,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -445,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -464,32 +460,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Versioning & Changelog
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -500,134 +474,6 @@
|
||||
|
||||
|
||||
</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="#the-version-contract" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The version contract
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="The version contract">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pre-10-0yz" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pre-1.0 (0.y.z)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#the-changelog" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The changelog
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="The changelog">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#reconcile-before-releasing" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Reconcile before releasing
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#tagging-strategy-mr-based" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Tagging strategy (MR-based)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="Tagging strategy (MR-based)">
|
||||
<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="#release-checklist" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Release checklist
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#why-the-guard-matters" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Why the guard matters
|
||||
</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>
|
||||
|
||||
|
||||
@@ -639,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -661,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -683,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -705,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -878,8 +724,8 @@ releases in a <a href="https://keepachangelog.com/en/1.1.0/">Keep a Changelog</a
|
||||
The <strong>version file is the source of truth</strong>, the <strong>git tag must equal it</strong>, and
|
||||
a Drone guard enforces that equality before anything is published. Releases are
|
||||
cut manually; a tag is what triggers the pipeline.</p>
|
||||
<p>This page pairs with <a href="../10_drone_pipelines/">10 – Drone Pipelines</a>: the version
|
||||
and tag contract is exactly what those two CI shapes consume.</p>
|
||||
<p>This page pairs with <a href="../90_drone_pipelines/">90 – Drone Pipelines</a>: the version
|
||||
and tag contract is exactly what those three CI shapes consume.</p>
|
||||
<hr />
|
||||
<h2 id="the-version-contract">The version contract</h2>
|
||||
<table>
|
||||
@@ -897,6 +743,11 @@ and tag contract is exactly what those two CI shapes consume.</p>
|
||||
<td><code>check-version</code> also reads it to skip duplicates</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Library (npm package)</td>
|
||||
<td><code>version</code> in <code>package.json</code></td>
|
||||
<td><code>check-version</code> also queries the registry to skip duplicates</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Service (Docker)</td>
|
||||
<td><code>VERSION</code> file at the repo root</td>
|
||||
<td>one line, <code>x.y.z</code>; the service <strong>reads</strong> it (build-arg) — never hardcode the version</td>
|
||||
@@ -992,7 +843,8 @@ bump travels with the change and only the tag is added afterwards:</p>
|
||||
<li><strong>In the MR</strong> — reconcile <code>[Unreleased]</code> against the change (see
|
||||
<a href="#reconcile-before-releasing">Reconcile before releasing</a>) and get the user's
|
||||
approval for any additions. Then set the intended release version: <code>version</code>
|
||||
in <code>pyproject.toml</code> (or the <code>VERSION</code> file) to <code>x.y.z</code>, and promote the
|
||||
in <code>pyproject.toml</code> or <code>package.json</code> (or the <code>VERSION</code> file) to <code>x.y.z</code>,
|
||||
and promote the
|
||||
changelog (below). Changelog entries and link references name the
|
||||
<strong>version/tag</strong>, never a commit id.</li>
|
||||
<li><strong>Merge or squash</strong> the MR into <code>main</code>.</li>
|
||||
@@ -1007,8 +859,10 @@ After the user confirms the MR is merged/squashed and says to proceed:</p>
|
||||
<ol>
|
||||
<li><strong>Wait</strong> for that explicit go-ahead.</li>
|
||||
<li>Find the commit that introduced the version bump:</li>
|
||||
<li>packages:
|
||||
<li>Python packages:
|
||||
<code>git log -S 'version = "x.y.z"' -n 1 --format='%H %s' -- pyproject.toml</code></li>
|
||||
<li>npm packages:
|
||||
<code>git log -S '"version": "x.y.z"' -n 1 --format='%H %s' -- package.json</code></li>
|
||||
<li>services:
|
||||
<code>git log -S 'x.y.z' -n 1 --format='%H %s' -- VERSION</code></li>
|
||||
<li>Confirm that commit's version file equals <code>x.y.z</code>, then tag <strong>only</strong> that
|
||||
@@ -1027,13 +881,14 @@ After the user confirms the MR is merged/squashed and says to proceed:</p>
|
||||
on top.</li>
|
||||
<li>In the MR: add the two compare-link references at the bottom of the
|
||||
changelog, naming the version/tag (never a commit id).</li>
|
||||
<li>In the MR: set the version file — <code>pyproject.toml</code> (<code>version = "x.y.z"</code>) or
|
||||
<code>VERSION</code> — to the intended release version.</li>
|
||||
<li>In the MR: set the version file — <code>pyproject.toml</code> (<code>version = "x.y.z"</code>),
|
||||
<code>package.json</code> (<code>"version": "x.y.z"</code>), or <code>VERSION</code> — to the intended
|
||||
release version.</li>
|
||||
<li>Merge or squash the MR into <code>main</code>.</li>
|
||||
<li>Create the tag on the resulting commit: lightweight <code>git tag x.y.z</code>.</li>
|
||||
<li>Push the tag (<code>git push origin x.y.z</code>). Watch Drone: Type A publishes the
|
||||
wheel/sdist, Type B builds and deploys the image; both first assert
|
||||
<code>tag == version</code>.</li>
|
||||
wheel/sdist, Type B publishes the npm package, Type C builds and deploys
|
||||
the image; all three first assert <code>tag == version</code>.</li>
|
||||
<li>Do not reuse the tag if the run fails — fix and cut <code>x.y.(z+1)</code>.</li>
|
||||
</ol>
|
||||
<hr />
|
||||
@@ -1047,7 +902,7 @@ Drone guard turns that silent no-op into a hard failure.</p>
|
||||
<hr />
|
||||
<h2 id="related">Related</h2>
|
||||
<ul>
|
||||
<li><a href="../10_drone_pipelines/">10 – Drone Pipelines</a> · <a href="../15_repository_inventory/">15 – Repository Inventory</a> · <a href="../01_build_recipe/">01 – Build Recipe</a> · <a href="../13_library_registry/">13 – Library Registry</a></li>
|
||||
<li><a href="../90_drone_pipelines/">90 – Drone Pipelines</a> · <a href="../94_repository_inventory/">94 – Repository Inventory</a> · <a href="../01_build_recipe/">01 – Build Recipe</a> · <a href="../93_library_registry/">93 – Library Registry</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../15_repository_inventory/">
|
||||
|
||||
|
||||
<link rel="next" href="../14_auth_integration/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -357,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -379,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -401,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -423,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -445,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -467,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -489,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -508,32 +504,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Genericity Matrix
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -544,80 +518,6 @@
|
||||
|
||||
|
||||
</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="#tier-a-library-backed-use-never-reimplement" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Tier A — Library-backed (use; never reimplement)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#tier-b-template-provided-copy-from-templatesapp-then-adapt" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Tier B — Template-provided (copy from templates/app, then adapt)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#tier-b-patterns-port-from-the-reference-app" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Tier B (patterns) — port from the reference app
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#tier-c-app-specific-must-be-authored" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Tier C — App-specific (must be authored)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#the-two-axes" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
The two axes
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -629,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -651,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../14_auth_integration/">
|
||||
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -267,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -289,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -311,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -333,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -355,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -377,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -399,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -421,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -443,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -465,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -487,7 +485,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory/" class="md-nav__link">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -509,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -531,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -550,32 +548,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
Library Registry
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -586,71 +562,6 @@
|
||||
|
||||
|
||||
</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="#catalog" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Catalog
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#how-to-choose" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
How to choose
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#discovery" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Discovery
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#notes" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Notes
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -715,6 +626,48 @@
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#publishing-registry-auth" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Publishing & registry auth
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="Publishing & registry auth">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#endpoints" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Endpoints
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#npm-scope-the-registry-never-set-it-globally" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
npm: scope the registry, never set it globally
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#verdaccio-storage-permissions" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Verdaccio storage permissions
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
@@ -910,6 +863,76 @@ matching library, its import name, its "use instead of" guidance, and its MCP
|
||||
endpoint (or, for the auth-server service, a spec pointer). It also serves the
|
||||
pinned auth-server OpenAPI contract at <code>docs://spec</code>. Query it before writing
|
||||
any infrastructure.</p>
|
||||
<h2 id="publishing-registry-auth">Publishing & registry auth</h2>
|
||||
<p>Python packages publish to the private index (<code>pip.aetoskia.com</code>, <code>pypiserver</code>)
|
||||
and npm packages to the private registry (<code>npm.aetoskia.com</code>, Verdaccio). Both
|
||||
authenticate against the <strong>same <code>htpasswd</code> file</strong> (<code>/auth/htpasswd</code>) used by the
|
||||
Docker registry and the Traefik basic-auth middleware; registration is disabled,
|
||||
so the CI credentials (<code>PIP_USERNAME</code> / <code>PIP_PASSWORD</code>, <code>NPM_USERNAME</code> /
|
||||
<code>NPM_PASSWORD</code>) are one and the same account — there is no separate npm user.
|
||||
Verdaccio requires <strong>bcrypt</strong> htpasswd entries; see
|
||||
<a href="../90_drone_pipelines/">90 – Drone Pipelines</a> for the CI wiring.</p>
|
||||
<h3 id="endpoints">Endpoints</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Registry</th>
|
||||
<th>URL</th>
|
||||
<th>Backend</th>
|
||||
<th>CI secret</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>PyPI</td>
|
||||
<td><code>https://pip.aetoskia.com</code></td>
|
||||
<td>pypiserver (<code>server-pi:6007</code>)</td>
|
||||
<td><code>PIP_REPO_URL</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>npm</td>
|
||||
<td><code>https://npm.aetoskia.com</code></td>
|
||||
<td>Verdaccio (<code>server-pi:6008</code>)</td>
|
||||
<td><code>NPM_REPO_URL</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Both are exposed through Traefik on the <code>websecure</code> entrypoint as <strong>bare
|
||||
origins</strong> — no path, no trailing slash. Set the secret values exactly as above.</p>
|
||||
<h3 id="npm-scope-the-registry-never-set-it-globally">npm: scope the registry, never set it globally</h3>
|
||||
<p>Verdaccio here has <strong>no uplink / proxy</strong>, so it can only serve <code>@aetoskia/*</code>
|
||||
packages. A global <code>registry=https://npm.aetoskia.com</code> in <code>.npmrc</code> would route
|
||||
<em>every</em> dependency — including public transitive deps such as
|
||||
<code>zod-to-json-schema</code> (pulled in by <code>@modelcontextprotocol/sdk</code>) — through
|
||||
Verdaccio, which 404s on anything it does not host. Always scope the registry to
|
||||
the account:</p>
|
||||
<div class="language-text 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>@aetoskia:registry=https://npm.aetoskia.com
|
||||
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2"></a>//npm.aetoskia.com/:_auth=<base64(user:password)>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>Public deps then resolve from <code>registry.npmjs.org</code> and only <code>@aetoskia/*</code> hits
|
||||
the private registry. The auth line must be <strong>scoped to the host</strong>
|
||||
(<code>//host/:_auth=</code>), not a bare <code>_auth=</code> — modern npm rejects the bare form with
|
||||
<code>ERR_INVALID_AUTH</code>. The drone template derives both from the package scope and
|
||||
<code>NPM_REPO_URL</code> automatically; see
|
||||
<a href="../90_drone_pipelines/">90 – Drone Pipelines</a>.</p>
|
||||
<blockquote>
|
||||
<p>The Drone pipeline runs each <code>commands:</code> list item in a <strong>separate shell</strong>, so
|
||||
shell variables defined in one item do not survive into the next. Keep the
|
||||
variable definitions and the commands that use them in the same <code>|</code> block.</p>
|
||||
</blockquote>
|
||||
<h3 id="verdaccio-storage-permissions">Verdaccio storage permissions</h3>
|
||||
<p>Verdaccio runs as uid <strong>10001</strong> (<code>verdaccio</code>) inside <code>verdaccio/verdaccio</code>.
|
||||
The <code>storage</code> directory is bind-mounted from the server
|
||||
(<code>/mnt/omnissiah-vault/data/verdaccio/storage</code>); if it is owned by <code>root:root</code>
|
||||
(mode <code>755</code>) the process cannot write to it and every publish fails with a
|
||||
confusing <code>404 no such package available</code> (<code>NotFoundError</code> from
|
||||
<code>local-storage</code>). Ensure the directory — and its <code>plugins</code> subdirectory — are
|
||||
owned by <code>10001:101</code>:</p>
|
||||
<div class="language-sh highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-1-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a>sudo<span class="w"> </span>chown<span class="w"> </span>-R<span class="w"> </span><span class="m">10001</span>:101<span class="w"> </span>/mnt/omnissiah-vault/data/verdaccio/storage
|
||||
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a>docker<span class="w"> </span>restart<span class="w"> </span>npm-registry
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<h2 id="notes">Notes</h2>
|
||||
<ul>
|
||||
<li><strong>Hexa</strong> is a full library with its own MCP server on <code>:8008</code>: read
|
||||
@@ -11,10 +11,6 @@
|
||||
|
||||
|
||||
|
||||
<link rel="prev" href="../11_versioning_and_changelog/">
|
||||
|
||||
|
||||
<link rel="next" href="../12_genericity_matrix/">
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/images/favicon.png">
|
||||
@@ -269,7 +265,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../02_layering_rules/" class="md-nav__link">
|
||||
<a href="../02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -291,7 +287,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../03_composition_root/" class="md-nav__link">
|
||||
<a href="../03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -313,7 +309,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="../04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +331,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../05_crud_template/" class="md-nav__link">
|
||||
<a href="../05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -357,7 +353,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="../06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -379,7 +375,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="../07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -401,7 +397,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../08_human_in_loop/" class="md-nav__link">
|
||||
<a href="../08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -423,7 +419,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="../09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -445,7 +441,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="../10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -467,7 +463,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="../11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -486,32 +482,10 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
<li class="md-nav__item">
|
||||
<a href="../15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -522,80 +496,6 @@
|
||||
|
||||
|
||||
</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-type-b" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Services (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>
|
||||
|
||||
|
||||
@@ -607,7 +507,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="../12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -629,7 +529,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../14_auth_integration/" class="md-nav__link">
|
||||
<a href="../14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -651,7 +551,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../13_library_registry/" class="md-nav__link">
|
||||
<a href="../13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -702,18 +602,27 @@
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#packages-type-a" class="md-nav__link">
|
||||
<a href="#packages-type-a-pypi" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Packages (Type A)
|
||||
Packages (Type A — PyPI)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#services-type-b" class="md-nav__link">
|
||||
<a href="#packages-type-b-npm" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Services (Type B)
|
||||
Packages (Type B — npm)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#services-type-c" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Services (Type C)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@@ -767,13 +676,13 @@
|
||||
<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 & Changelog</a> and the two CI
|
||||
files in <a href="../10_drone_pipelines/">10 – Drone Pipelines</a>.</p>
|
||||
<a href="../91_versioning_and_changelog/">91 – Versioning & Changelog</a> and the three CI
|
||||
files in <a href="../90_drone_pipelines/">90 – 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>
|
||||
<h2 id="packages-type-a-pypi">Packages (Type A — PyPI)</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>
|
||||
@@ -837,7 +746,41 @@ Paths are relative to the <code>aetos/</code> workspace root.</p>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h2 id="services-type-b">Services (Type B)</h2>
|
||||
<h2 id="packages-type-b-npm">Packages (Type B — npm)</h2>
|
||||
<p>A package published to the private npm registry (<code>npm.aetoskia.com</code>). Version
|
||||
source is <code>package.json</code>; Drone file is <code>npm-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>doc-weaver</code></td>
|
||||
<td><code>doc-weaver</code></td>
|
||||
<td>lib, wiki</td>
|
||||
<td><code>doc-weaver:8010</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>react-auth</code></td>
|
||||
<td><code>react/react-auth</code></td>
|
||||
<td>lib, wiki</td>
|
||||
<td><code>react-auth:8011</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>react-openapi</code></td>
|
||||
<td><code>react/react-openapi</code></td>
|
||||
<td>lib, wiki</td>
|
||||
<td><code>react-openapi:8012</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h2 id="services-type-c">Services (Type C)</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>
|
||||
@@ -872,12 +815,18 @@ source is the <code>VERSION</code> file; Drone file is <code>service-docker.yaml
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>package</td>
|
||||
<td>package (PyPI)</td>
|
||||
<td><code>pyproject.toml</code></td>
|
||||
<td><code>CHANGELOG.md</code></td>
|
||||
<td><code>python-lib.yaml</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>package (npm)</td>
|
||||
<td><code>package.json</code></td>
|
||||
<td><code>CHANGELOG.md</code></td>
|
||||
<td><code>npm-lib.yaml</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>service</td>
|
||||
<td><code>VERSION</code></td>
|
||||
<td><code>CHANGELOG.md</code></td>
|
||||
@@ -898,7 +847,7 @@ merged, and the tag is created on the merged commit.</p>
|
||||
<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 & Changelog</a> · <a href="../13_library_registry/">13 – Library Registry</a></li>
|
||||
<li><a href="../90_drone_pipelines/">90 – Drone Pipelines</a> · <a href="../91_versioning_and_changelog/">91 – Versioning & Changelog</a> · <a href="../93_library_registry/">93 – Library Registry</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -232,6 +232,19 @@
|
||||
|
||||
|
||||
|
||||
<label class="md-nav__link md-nav__link--active" for="__toc">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Home
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<a href="." class="md-nav__link md-nav__link--active">
|
||||
|
||||
|
||||
@@ -244,6 +257,88 @@
|
||||
|
||||
</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="#anatomy" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Anatomy
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pillar-1-domains" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pillar 1 — Domains
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pillar-2-openapi-spec-the-contract" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pillar 2 — OpenAPI Spec (the contract)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pillar-3-interfaces-infra" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pillar 3 — Interfaces & Infra
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pillar-4-ui" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pillar 4 — UI
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pipelines-optional-layer" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pipelines (optional layer)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#delivery-meta" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Delivery & Meta
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
@@ -277,7 +372,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="02_layering_rules/" class="md-nav__link">
|
||||
<a href="02_layering_rules.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -299,7 +394,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="03_composition_root/" class="md-nav__link">
|
||||
<a href="03_composition_root.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -321,7 +416,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="04_spec_first_routing/" class="md-nav__link">
|
||||
<a href="04_spec_first_routing.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -343,7 +438,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="05_crud_template/" class="md-nav__link">
|
||||
<a href="05_crud_template.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -365,7 +460,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="06_pipeline_scaffold/" class="md-nav__link">
|
||||
<a href="06_pipeline_scaffold.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -387,7 +482,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="07_worker_and_progress/" class="md-nav__link">
|
||||
<a href="07_worker_and_progress.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -409,7 +504,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="08_human_in_loop/" class="md-nav__link">
|
||||
<a href="08_human_in_loop.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -431,7 +526,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="09_infra_adaptors/" class="md-nav__link">
|
||||
<a href="09_infra_adaptors.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -453,7 +548,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="10_drone_pipelines/" class="md-nav__link">
|
||||
<a href="10_drone_pipelines.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -475,7 +570,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="11_versioning_and_changelog/" class="md-nav__link">
|
||||
<a href="11_versioning_and_changelog.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -497,7 +592,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="15_repository_inventory/" class="md-nav__link">
|
||||
<a href="15_repository_inventory.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -519,7 +614,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="12_genericity_matrix/" class="md-nav__link">
|
||||
<a href="12_genericity_matrix.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -541,7 +636,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="14_auth_integration/" class="md-nav__link">
|
||||
<a href="14_auth_integration.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -563,7 +658,7 @@
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="13_library_registry/" class="md-nav__link">
|
||||
<a href="13_library_registry.md" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
@@ -598,6 +693,77 @@
|
||||
|
||||
|
||||
|
||||
<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="#anatomy" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Anatomy
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pillar-1-domains" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pillar 1 — Domains
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pillar-2-openapi-spec-the-contract" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pillar 2 — OpenAPI Spec (the contract)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pillar-3-interfaces-infra" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pillar 3 — Interfaces & Infra
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pillar-4-ui" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pillar 4 — UI
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#pipelines-optional-layer" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Pipelines (optional layer)
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#delivery-meta" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
Delivery & Meta
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
@@ -615,26 +781,54 @@
|
||||
<h1 id="standards-reference">Standards Reference</h1>
|
||||
<p>The reference half of the Standards description: the enforceable rules,
|
||||
patterns, and the internal library catalog that agents consult while coding.</p>
|
||||
<p>The reference is organized as an <strong>anatomy</strong> — one entry checklist and four
|
||||
pillars describing what an application is made of, plus delivery/meta pages
|
||||
and an optional pipelines layer.</p>
|
||||
<h2 id="anatomy">Anatomy</h2>
|
||||
<ul>
|
||||
<li><strong>Build Recipe</strong> — the ordered, agentic checklist for a new app; the entry
|
||||
point that walks the whole anatomy.</li>
|
||||
</ul>
|
||||
<h2 id="pillar-1-domains">Pillar 1 — Domains</h2>
|
||||
<ul>
|
||||
<li><strong>Build Recipe</strong> — the ordered, agentic checklist for a new app.</li>
|
||||
<li><strong>Layering Rules</strong> — the strict request → domain → repository layering and
|
||||
the rules that enforce it.</li>
|
||||
</ul>
|
||||
<h2 id="pillar-2-openapi-spec-the-contract">Pillar 2 — OpenAPI Spec (the contract)</h2>
|
||||
<ul>
|
||||
<li><strong>Composition Root</strong> — a single composition point wiring the whole app.</li>
|
||||
<li><strong>Spec-First Routing</strong> — the OpenAPI contract as the routing source of truth.</li>
|
||||
<li><strong>CRUD Template</strong> — the standardized generic resource endpoint scaffold.</li>
|
||||
<li><strong>Auth Integration</strong> — consuming the shared auth-server service (spec-driven,
|
||||
no auth code).</li>
|
||||
</ul>
|
||||
<h2 id="pillar-3-interfaces-infra">Pillar 3 — Interfaces & Infra</h2>
|
||||
<ul>
|
||||
<li><strong>Infra Adapters</strong> — how infrastructure connects without leaking into domain.</li>
|
||||
</ul>
|
||||
<h2 id="pillar-4-ui">Pillar 4 — UI</h2>
|
||||
<ul>
|
||||
<li><strong>UI: Auto-Rendered Admin & Custom Pages</strong> — the <code>@aetoskia/react-openapi</code>
|
||||
admin rendered from the spec's x- extensions, and <code>@aetoskia/react-auth</code>
|
||||
for auth pages; the client template scaffold.</li>
|
||||
</ul>
|
||||
<h2 id="pipelines-optional-layer">Pipelines (optional layer)</h2>
|
||||
<ul>
|
||||
<li><strong>Pipeline Scaffold</strong> — the deterministic pipeline skeleton for multi-stage
|
||||
jobs.</li>
|
||||
<li><strong>Worker & 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 & Changelog</strong> — SemVer, the tag↔version contract, and the release
|
||||
changelog.</li>
|
||||
</ul>
|
||||
<h2 id="delivery-meta">Delivery & Meta</h2>
|
||||
<ul>
|
||||
<li><strong>Drone Pipelines</strong> — the three CI shapes: a PyPI package, an npm package,
|
||||
or a Docker deployment.</li>
|
||||
<li><strong>Versioning & 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>
|
||||
<li><strong>Genericity Matrix</strong> — what is library-backed, template-provided, or
|
||||
authored.</li>
|
||||
<li><strong>Library Registry</strong> — the catalog of internal libraries behind all of it.</li>
|
||||
</ul>
|
||||
<p>Read the <strong>Wiki</strong> for the philosophy and rationale; these pages are the
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
102
standards/mcp/client.json
Normal file
102
standards/mcp/client.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"project": "standards",
|
||||
"template": "templates/client",
|
||||
"components": [
|
||||
{
|
||||
"kind": "client-package",
|
||||
"path": "package.json",
|
||||
"placeholder": "",
|
||||
"description": "npm manifest: @aetoskia deps, scripts (dev/build/preview)."
|
||||
},
|
||||
{
|
||||
"kind": "client-npmrc",
|
||||
"path": ".npmrc",
|
||||
"placeholder": "",
|
||||
"description": "Private registry auth pointing at npm.aetoskia.com."
|
||||
},
|
||||
{
|
||||
"kind": "client-env",
|
||||
"path": ".env.example",
|
||||
"placeholder": "",
|
||||
"description": "VITE_API_BASE_URL / VITE_AUTH_BASE_URL seed."
|
||||
},
|
||||
{
|
||||
"kind": "client-vite-config",
|
||||
"path": "vite.config.ts",
|
||||
"placeholder": "",
|
||||
"description": "Vite config (dev server, build)."
|
||||
},
|
||||
{
|
||||
"kind": "client-tsconfig",
|
||||
"path": "tsconfig.json",
|
||||
"placeholder": "",
|
||||
"description": "TypeScript config for the client."
|
||||
},
|
||||
{
|
||||
"kind": "client-index-html",
|
||||
"path": "index.html",
|
||||
"placeholder": "",
|
||||
"description": "HTML entry point."
|
||||
},
|
||||
{
|
||||
"kind": "client-entry",
|
||||
"path": "src/main.jsx",
|
||||
"placeholder": "",
|
||||
"description": "React entry: router + providers + theme."
|
||||
},
|
||||
{
|
||||
"kind": "client-openapi-config",
|
||||
"path": "src/openapi-config.ts",
|
||||
"placeholder": "",
|
||||
"description": "Points the admin renderer at the app spec + auth-server spec."
|
||||
},
|
||||
{
|
||||
"kind": "client-home",
|
||||
"path": "src/Home.tsx",
|
||||
"placeholder": "",
|
||||
"description": "Auto-listing home page (ResourceCard + LiveTable per resource)."
|
||||
},
|
||||
{
|
||||
"kind": "client-header",
|
||||
"path": "src/Header.tsx",
|
||||
"placeholder": "",
|
||||
"description": "App header bar (brand + nav)."
|
||||
},
|
||||
{
|
||||
"kind": "client-footer",
|
||||
"path": "src/Footer.tsx",
|
||||
"placeholder": "",
|
||||
"description": "App footer."
|
||||
},
|
||||
{
|
||||
"kind": "client-vite-env",
|
||||
"path": "src/vite-env.d.ts",
|
||||
"placeholder": "",
|
||||
"description": "Vite client type declarations."
|
||||
},
|
||||
{
|
||||
"kind": "client-theme",
|
||||
"path": "src/shared-theme/AppTheme.tsx",
|
||||
"placeholder": "",
|
||||
"description": "Theme provider wrapper (light/dark)."
|
||||
},
|
||||
{
|
||||
"kind": "client-theme-config",
|
||||
"path": "src/shared-theme/themeConfig.ts",
|
||||
"placeholder": "",
|
||||
"description": "Palette / typography tokens."
|
||||
},
|
||||
{
|
||||
"kind": "client-theme-primitives",
|
||||
"path": "src/shared-theme/themePrimitives.ts",
|
||||
"placeholder": "",
|
||||
"description": "MUI theme primitives (shadows, accents)."
|
||||
},
|
||||
{
|
||||
"kind": "client-theme-shim",
|
||||
"path": "src/shared-theme/mui.d.ts",
|
||||
"placeholder": "",
|
||||
"description": "MUI v5 type shims (Theme.vars, SelectClasses.focused)."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,40 +1,91 @@
|
||||
{
|
||||
"project": "standards",
|
||||
"template": "templates/app",
|
||||
"seed": {
|
||||
"resource": "item",
|
||||
"family": "example"
|
||||
},
|
||||
"sample": "vet",
|
||||
"components": [
|
||||
{
|
||||
"kind": "model",
|
||||
"path": "domain/item/models.py",
|
||||
"placeholder": "resource",
|
||||
"description": "Business Pydantic model for a resource."
|
||||
"kind": "parent-model",
|
||||
"path": "domain/parents/models.py",
|
||||
"placeholder": "",
|
||||
"description": "Example domain model (parents — the simplest slice)."
|
||||
},
|
||||
{
|
||||
"kind": "ports-db",
|
||||
"path": "domain/item/ports/db.py",
|
||||
"placeholder": "resource",
|
||||
"description": "Document model + repository factory for a resource."
|
||||
"kind": "parent-ports-db",
|
||||
"path": "domain/parents/ports/db.py",
|
||||
"placeholder": "",
|
||||
"description": "Example document model + repository factory."
|
||||
},
|
||||
{
|
||||
"kind": "ports-service",
|
||||
"path": "domain/item/ports/service.py",
|
||||
"placeholder": "resource",
|
||||
"description": "CRUD service wired through the DI library."
|
||||
"kind": "parent-ports-service",
|
||||
"path": "domain/parents/ports/service.py",
|
||||
"placeholder": "",
|
||||
"description": "Example CRUD service wired through the DI library."
|
||||
},
|
||||
{
|
||||
"kind": "converters",
|
||||
"path": "domain/item/ports/converters.py",
|
||||
"placeholder": "resource",
|
||||
"description": "Optional pure converters between domain types."
|
||||
"kind": "parent-routes",
|
||||
"path": "interfaces/parents/routes.py",
|
||||
"placeholder": "",
|
||||
"description": "Example thin HTTP handlers bound to operationIds."
|
||||
},
|
||||
{
|
||||
"kind": "routes",
|
||||
"path": "interfaces/item/routes.py",
|
||||
"placeholder": "resource",
|
||||
"description": "Thin HTTP handlers bound to operationIds."
|
||||
"kind": "pet-model",
|
||||
"path": "domain/pets/models.py",
|
||||
"placeholder": "",
|
||||
"description": "Domain model with an embedded parents list."
|
||||
},
|
||||
{
|
||||
"kind": "pet-ports-db",
|
||||
"path": "domain/pets/ports/db.py",
|
||||
"placeholder": "",
|
||||
"description": "Populating document + repository factory (FK wiring)."
|
||||
},
|
||||
{
|
||||
"kind": "pet-routes",
|
||||
"path": "interfaces/pets/routes.py",
|
||||
"placeholder": "",
|
||||
"description": "Routes including the file-upload endpoint."
|
||||
},
|
||||
{
|
||||
"kind": "appointment-model",
|
||||
"path": "domain/appointments/models.py",
|
||||
"placeholder": "",
|
||||
"description": "Domain model with single-id FKs + nested value objects."
|
||||
},
|
||||
{
|
||||
"kind": "appointment-routes",
|
||||
"path": "interfaces/appointments/routes.py",
|
||||
"placeholder": "",
|
||||
"description": "Routes with expand_ref FK handling and list filters."
|
||||
},
|
||||
{
|
||||
"kind": "job-model",
|
||||
"path": "domain/jobs/models.py",
|
||||
"placeholder": "",
|
||||
"description": "Job queue model (the worker's unit of work)."
|
||||
},
|
||||
{
|
||||
"kind": "job-ports-db",
|
||||
"path": "domain/jobs/ports/db.py",
|
||||
"placeholder": "",
|
||||
"description": "Job repository factory."
|
||||
},
|
||||
{
|
||||
"kind": "job-ports-service",
|
||||
"path": "domain/jobs/ports/service.py",
|
||||
"placeholder": "",
|
||||
"description": "Job CRUD service."
|
||||
},
|
||||
{
|
||||
"kind": "job-routes",
|
||||
"path": "interfaces/jobs/routes.py",
|
||||
"placeholder": "",
|
||||
"description": "Job routes (enqueue/inspect via the API)."
|
||||
},
|
||||
{
|
||||
"kind": "interface-common",
|
||||
"path": "interfaces/_common.py",
|
||||
"placeholder": "",
|
||||
"description": "Shared helpers: svc resolution, dump, pagination, expand_ref."
|
||||
},
|
||||
{
|
||||
"kind": "openapi-routes",
|
||||
@@ -48,58 +99,34 @@
|
||||
"placeholder": "",
|
||||
"description": "Mergeable bearerAuth security scheme + global security for openapi.yaml."
|
||||
},
|
||||
{
|
||||
"kind": "test-model",
|
||||
"path": "tests/unit/test_item_models.py",
|
||||
"placeholder": "resource",
|
||||
"description": "Unit test for the resource model."
|
||||
},
|
||||
{
|
||||
"kind": "test-routes",
|
||||
"path": "tests/integration/test_item_routes.py",
|
||||
"placeholder": "resource",
|
||||
"description": "Integration test at the HTTP edge."
|
||||
},
|
||||
{
|
||||
"kind": "test-openapi-build",
|
||||
"path": "tests/integration/test_openapi_app_builds.py",
|
||||
"placeholder": "",
|
||||
"description": "Generic test: every spec operationId resolves and the app builds."
|
||||
},
|
||||
{
|
||||
"kind": "test-pipeline",
|
||||
"path": "tests/unit/test_item_sync.py",
|
||||
"placeholder": "",
|
||||
"description": "Unit test for the smallest pipeline form."
|
||||
},
|
||||
{
|
||||
"kind": "pipeline-abc",
|
||||
"path": "pipelines/example/abc.py",
|
||||
"placeholder": "family",
|
||||
"path": "pipelines/appointment_reminder/abc.py",
|
||||
"placeholder": "",
|
||||
"description": "Port contracts for a pipeline family."
|
||||
},
|
||||
{
|
||||
"kind": "pipeline-impl",
|
||||
"path": "pipelines/example/impl.py",
|
||||
"placeholder": "family",
|
||||
"path": "pipelines/appointment_reminder/impl.py",
|
||||
"placeholder": "",
|
||||
"description": "Concrete stages, wired root, and family registration."
|
||||
},
|
||||
{
|
||||
"kind": "pipeline-yaml",
|
||||
"path": "pipelines/example/pipeline.yaml",
|
||||
"placeholder": "family",
|
||||
"path": "pipelines/appointment_reminder/pipeline.yaml",
|
||||
"placeholder": "",
|
||||
"description": "Graph spec (nodes, edges, roots, schema)."
|
||||
},
|
||||
{
|
||||
"kind": "pipeline-resources",
|
||||
"path": "pipelines/example/resources/pipeline.yaml",
|
||||
"placeholder": "family",
|
||||
"path": "pipelines/appointment_reminder/resources/pipeline.yaml",
|
||||
"placeholder": "",
|
||||
"description": "Runtime spec consumed by the shared loader."
|
||||
},
|
||||
{
|
||||
"kind": "pipeline-pattern",
|
||||
"path": "pipelines/example/resources/patterns/example.yaml",
|
||||
"placeholder": "family",
|
||||
"path": "pipelines/appointment_reminder/resources/patterns/reminder.yaml",
|
||||
"placeholder": "",
|
||||
"description": "Pattern/rule fragment merged by the loader."
|
||||
},
|
||||
{
|
||||
@@ -120,47 +147,17 @@
|
||||
"placeholder": "",
|
||||
"description": "Shared registry: (kind, variant, mode) -> root class."
|
||||
},
|
||||
{
|
||||
"kind": "pipeline-simple",
|
||||
"path": "pipelines/item_sync.py",
|
||||
"placeholder": "",
|
||||
"description": "Smallest pipeline form (plain async steps)."
|
||||
},
|
||||
{
|
||||
"kind": "job-model",
|
||||
"path": "domain/job/models.py",
|
||||
"placeholder": "",
|
||||
"description": "Seeded second resource model (job)."
|
||||
},
|
||||
{
|
||||
"kind": "job-ports-db",
|
||||
"path": "domain/job/ports/db.py",
|
||||
"placeholder": "",
|
||||
"description": "Seeded second resource repository (job)."
|
||||
},
|
||||
{
|
||||
"kind": "job-ports-service",
|
||||
"path": "domain/job/ports/service.py",
|
||||
"placeholder": "",
|
||||
"description": "Seeded second resource service (job)."
|
||||
},
|
||||
{
|
||||
"kind": "job-routes",
|
||||
"path": "interfaces/job/routes.py",
|
||||
"placeholder": "",
|
||||
"description": "Seeded second resource routes (job)."
|
||||
},
|
||||
{
|
||||
"kind": "config",
|
||||
"path": "config.py",
|
||||
"placeholder": "",
|
||||
"description": "Settings dataclass + singleton."
|
||||
"description": "Settings dataclass + singleton (incl. worker knobs)."
|
||||
},
|
||||
{
|
||||
"kind": "init",
|
||||
"path": "init.py",
|
||||
"placeholder": "",
|
||||
"description": "Composition root (init/shutdown, get_svc)."
|
||||
"description": "Composition root (init/shutdown, resources dict)."
|
||||
},
|
||||
{
|
||||
"kind": "main",
|
||||
@@ -172,13 +169,13 @@
|
||||
"kind": "worker",
|
||||
"path": "worker.py",
|
||||
"placeholder": "",
|
||||
"description": "Poll loop + job status machine."
|
||||
"description": "Poll loop + job dispatch by kind + status machine."
|
||||
},
|
||||
{
|
||||
"kind": "openapi",
|
||||
"path": "openapi.yaml",
|
||||
"placeholder": "",
|
||||
"description": "OpenAPI contract skeleton."
|
||||
"description": "OpenAPI contract (x- extensions drive the admin client)."
|
||||
},
|
||||
{
|
||||
"kind": "pyproject",
|
||||
@@ -192,6 +189,12 @@
|
||||
"placeholder": "",
|
||||
"description": "Keep a Changelog file with an [Unreleased] section (versioning standard)."
|
||||
},
|
||||
{
|
||||
"kind": "version-file",
|
||||
"path": "VERSION",
|
||||
"placeholder": "",
|
||||
"description": "Single-line version file (service-docker CI checks tag == VERSION)."
|
||||
},
|
||||
{
|
||||
"kind": "dockerfile",
|
||||
"path": "Dockerfile",
|
||||
@@ -234,6 +237,18 @@
|
||||
"placeholder": "",
|
||||
"description": "Repository factory (base/populating/cached)."
|
||||
},
|
||||
{
|
||||
"kind": "repo-int",
|
||||
"path": "infra/db/repos/mongo_int.py",
|
||||
"placeholder": "",
|
||||
"description": "Integer-id repository variants (create_repo factory)."
|
||||
},
|
||||
{
|
||||
"kind": "repo-memory",
|
||||
"path": "infra/db/repos/memory.py",
|
||||
"placeholder": "",
|
||||
"description": "In-memory repository flavour (zero-dependency demo store)."
|
||||
},
|
||||
{
|
||||
"kind": "service-adapter",
|
||||
"path": "infra/adapters/service.py",
|
||||
@@ -252,6 +267,12 @@
|
||||
"placeholder": "",
|
||||
"description": "In-memory SSE progress bus (keyed by an event attribute)."
|
||||
},
|
||||
{
|
||||
"kind": "actions-stream",
|
||||
"path": "infra/adapters/actions_stream.py",
|
||||
"placeholder": "",
|
||||
"description": "Per-resource action stream (SSE) adaptor."
|
||||
},
|
||||
{
|
||||
"kind": "kv-store",
|
||||
"path": "infra/adapters/kv.py",
|
||||
@@ -275,6 +296,30 @@
|
||||
"path": "scripts/drop.py",
|
||||
"placeholder": "",
|
||||
"description": "Operator drop script."
|
||||
},
|
||||
{
|
||||
"kind": "test-openapi-build",
|
||||
"path": "tests/integration/test_openapi_app_builds.py",
|
||||
"placeholder": "",
|
||||
"description": "Generic test: every spec operationId resolves and the app builds."
|
||||
},
|
||||
{
|
||||
"kind": "test-jobs-routes",
|
||||
"path": "tests/unit/test_jobs_routes.py",
|
||||
"placeholder": "",
|
||||
"description": "Unit test pattern for a resource's routes with service fakes."
|
||||
},
|
||||
{
|
||||
"kind": "test-pipeline",
|
||||
"path": "tests/unit/test_appointment_reminder_pipeline.py",
|
||||
"placeholder": "",
|
||||
"description": "Unit test pattern for a pipeline family."
|
||||
},
|
||||
{
|
||||
"kind": "test-worker",
|
||||
"path": "tests/unit/test_worker.py",
|
||||
"placeholder": "",
|
||||
"description": "Unit test pattern for worker dispatch and status transitions."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"template": "templates/drone",
|
||||
"types": [
|
||||
"pypi",
|
||||
"npm",
|
||||
"docker"
|
||||
],
|
||||
"components": [
|
||||
@@ -12,6 +13,12 @@
|
||||
"placeholder": "",
|
||||
"description": "Drone pipeline: build + publish a Python package to the private PyPI index."
|
||||
},
|
||||
{
|
||||
"kind": "drone-npm",
|
||||
"path": "npm-lib.yaml",
|
||||
"placeholder": "",
|
||||
"description": "Drone pipeline: build + publish an npm package to the private npm registry."
|
||||
},
|
||||
{
|
||||
"kind": "drone-service",
|
||||
"path": "service-docker.yaml",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"project": "standards",
|
||||
"type": "standards",
|
||||
"modules_count": 23,
|
||||
"modules_count": 28,
|
||||
"source": "hand-authored"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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- **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",
|
||||
"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\nThe reference is organized as an **anatomy** — one entry checklist and four\npillars describing what an application is made of, plus delivery/meta pages\nand an optional pipelines layer.\n\n## Anatomy\n\n- **Build Recipe** — the ordered, agentic checklist for a new app; the entry\n point that walks the whole anatomy.\n\n## Pillar 1 — Domains\n\n- **Layering Rules** — the strict request → domain → repository layering and\n the rules that enforce it.\n\n## Pillar 2 — OpenAPI Spec (the contract)\n\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- **Auth Integration** — consuming the shared auth-server service (spec-driven,\n no auth code).\n\n## Pillar 3 — Interfaces & Infra\n\n- **Infra Adapters** — how infrastructure connects without leaking into domain.\n\n## Pillar 4 — UI\n\n- **UI: Auto-Rendered Admin & Custom Pages** — the `@aetoskia/react-openapi`\n admin rendered from the spec's x- extensions, and `@aetoskia/react-auth`\n for auth pages; the client template scaffold.\n\n## Pipelines (optional layer)\n\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\n## Delivery & Meta\n\n- **Drone Pipelines** — the three CI shapes: a PyPI package, an npm package,\n or a Docker deployment.\n- **Versioning & Changelog** — SemVer, the tag↔version contract, and the\n release 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\n authored.\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": {}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
"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 (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 | Path | Trigger | Docs |\n|------|------|---------|------|\n| `auth-server` | `auth-server` | tag | api, lib, wiki |\n\nTag-triggered and enforces `tag == VERSION`.\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 | `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",
|
||||
"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[91 – Versioning & Changelog](91_versioning_and_changelog.md) and the three CI\nfiles in [90 – Drone Pipelines](90_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 — PyPI)\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## Packages (Type B — npm)\n\nA package published to the private npm registry (`npm.aetoskia.com`). Version\nsource is `package.json`; Drone file is `npm-lib.yaml` (tag-triggered).\n\n| Repo | Path | Docs | MCP server |\n|------|------|------|------------|\n| `doc-weaver` | `doc-weaver` | lib, wiki | `doc-weaver:8010` |\n| `react-auth` | `react/react-auth` | lib, wiki | `react-auth:8011` |\n| `react-openapi` | `react/react-openapi` | lib, wiki | `react-openapi:8012` |\n\n---\n\n## Services (Type C)\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 | Path | Trigger | Docs |\n|------|------|---------|------|\n| `auth-server` | `auth-server` | tag | api, lib, wiki |\n\nTag-triggered and enforces `tag == VERSION`.\n\n---\n\n## Required files per repo\n\n| Type | Version source | Changelog | Drone |\n|------|----------------|-----------|-------|\n| package (PyPI) | `pyproject.toml` | `CHANGELOG.md` | `python-lib.yaml` |\n| package (npm) | `package.json` | `CHANGELOG.md` | `npm-lib.yaml` |\n| service | `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- [90 – Drone Pipelines](90_drone_pipelines.md) · [91 – Versioning & Changelog](91_versioning_and_changelog.md) · [93 – Library Registry](93_library_registry.md)\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
8
standards/mcp/modules/ui_admin.json
Normal file
8
standards/mcp/modules/ui_admin.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"module": "ui_admin",
|
||||
"content": {
|
||||
"path": "ui_admin",
|
||||
"docstring": "# UI: Auto-Rendered Admin & Custom Pages\n\n## Summary\n\nThe UI axis of an Aetos app is a **published React client**, not code written\nper application. Two npm packages carry the whole surface:\n\n| Package | Role |\n|---|---|\n| `@aetoskia/react-openapi` | Renders a complete admin panel (CRUD tables, forms, filters, sorting, pagination, uploads) **from the app's `openapi.yaml`** — zero UI code. |\n| `@aetoskia/react-auth` | Auth contexts and pages (login/register/me) wired to the auth-server, plus the axios client with token handling. |\n\nBoth publish to the private registry `https://npm.aetoskia.com`. The server\nspec is the single source of truth: the same `openapi.yaml` that drives\n`openapi-first` routing drives the admin UI, through **x- extension fields**\n(see below).\n\n---\n\n## Scaffold\n\n```bash\ncp -r <docs>/standards/src/templates/client apps/<your-app>-client\ncd apps/<your-app>-client\nnpm install # deps come from npm.aetoskia.com via .npmrc\nnpm run dev\n```\n\nThe client template is a Vite + MUI app with:\n\n- `src/openapi-config.ts` — points the renderer at the app's spec and the\n auth-server's spec;\n- `.env.example` — `VITE_API_BASE_URL` (app API) and `VITE_AUTH_BASE_URL`\n (auth-server);\n- a `Home` page that auto-lists every resource the spec declares, and a\n `Header`/`Footer`/theme scaffold.\n\nCustom pages are ordinary React routes layered on top; the auto-rendered admin\nis available per resource at `#/resource/<name>`.\n\n---\n\n## x- extensions: how the spec steers the UI\n\nThe admin renderer reads a catalog of OpenAPI extension fields (`x-resource`,\n`x-list-columns`, `x-display-format`, `x-label`, `x-order`, `x-hidden`,\n`x-sortable`, `x-filterable`, `x-autocomplete`, `x-fk`, `x-ui-type`,\n`x-upload`, `x-sse`, `x-profile`, and the auth wiring `x-server-url` /\n`x-login-path` / `x-me-path` / …) declared on schemas and properties.\n\n**The catalog is canonical in the react-openapi source, not in this doc.**\nRead it from the MCP bundle:\n\n```\ndocs://modules/react-openapi # module: specExtensions — the full catalog\n```\n\nThe app's own spec carries the per-resource values; the catalog explains what\neach field does. The template's `openapi.yaml` shows the convention on the\nexample `Job` schema.\n\n---\n\n## Golden rules\n\n1. **The spec owns the UI.** Add a resource or rename a column in\n `openapi.yaml`; the admin follows. Never fork the renderer per app.\n2. **Custom pages for workflows, auto-render for CRUD.** Anything that is\n \"list / create / edit / delete an entity\" is a spec concern; anything that\n is a bespoke workflow is a React page.\n3. **Auth is consumed, never re-implemented.** `@aetoskia/react-auth` holds\n tokens and calls the auth-server endpoints declared by the x- wiring; pages\n read the context.\n4. **Publish, don't vendor.** UI shared across apps belongs in\n `@aetoskia/react-openapi` or `@aetoskia/react-auth` (SemVer, tag-triggered\n npm release), not copy-pasted into app trees.\n\n---\n\n## Notes\n\n- The server template ships authless by default (see\n [22 – Auth Integration](22_auth_integration.md)); the client template pairs\n with it and degrades to \"no login\" until a security scheme is declared.\n- The standards **app + client templates** are the full-stack reference: the\n server spec carries the x- extensions, and the client renders the admin from\n it. The wiki's **Reference App** walkthrough (`06_reference_app`) documents\n the pairing end to end.\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
"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 → `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 all edits **in the MR**:\n\n- **Reconcile the changelog first.** `[Unreleased]` is a **draft, not the source\n of truth**: diff the change against the last tag\n (`git log <last-tag>..HEAD`, `git diff --stat <last-tag>..HEAD`) and add an\n entry for every user-facing change that is missing. **Always show the proposed\n additions to the user and wait for approval** before promoting — ask rather\n than guess when an entry is ambiguous.\n\n- **Promote the changelog** in `CHANGELOG.md`: move the (now reconciled)\n `[Unreleased]` content under a new `## [x.y.z] - YYYY-MM-DD` heading, leave a\n fresh empty `[Unreleased]` on top, and add the compare links — naming the\n **version/tag**, 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).\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:\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\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",
|
||||
"docstring": "# Releasing & Versioning\n\nHow to cut a release of any Aetos repository. The rule lives in the reference\npage [Versioning & Changelog](../lib/91_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/90_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 → `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 all edits **in the MR**:\n\n- **Reconcile the changelog first.** `[Unreleased]` is a **draft, not the source\n of truth**: diff the change against the last tag\n (`git log <last-tag>..HEAD`, `git diff --stat <last-tag>..HEAD`) and add an\n entry for every user-facing change that is missing. **Always show the proposed\n additions to the user and wait for approval** before promoting — ask rather\n than guess when an entry is ambiguous.\n\n- **Promote the changelog** in `CHANGELOG.md`: move the (now reconciled)\n `[Unreleased]` content under a new `## [x.y.z] - YYYY-MM-DD` heading, leave a\n fresh empty `[Unreleased]` on top, and add the compare links — naming the\n **version/tag**, 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).\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:\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\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 matching `backfill-*-from-tags` pipeline (custom event) —\n `backfill-pypi-from-tags` for Python, `backfill-npm-from-tags` for npm. It\n checks out every tag, verifies `tag ==` the version file at that commit, and\n 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/91_versioning_and_changelog/) · [Drone Pipelines](../lib/90_drone_pipelines/)\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"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",
|
||||
"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| [Reference App](06_reference_app/01_overview.md) | The runnable vet sample — server, pipelines, client |\n| [Testing & Quality](04_development/01_testing.md) | Test layout and acceptance gates |\n\n**Reference** (separate site, mirrors the MCP bundle): the enforceable\nanatomy — start at the [Build Recipe](../lib/01_build_recipe/), then the\npillars: domains ([Layering](../lib/10_layering_rules/)), the OpenAPI\ncontract ([Composition Root](../lib/02_composition_root/),\n[Spec-First Routing](../lib/20_spec_first_routing/), [CRUD\nTemplate](../lib/21_crud_template/), [Auth\nIntegration](../lib/22_auth_integration/)), interfaces & infra ([Infra\nAdapters](../lib/30_infra_adaptors/)), UI ([Auto-Rendered\nAdmin](../lib/40_ui_admin/)), the optional pipelines layer ([Pipeline\nScaffold](../lib/50_pipeline_scaffold/), [Worker &\nProgress](../lib/51_worker_and_progress/), [Human-in-the-Loop](../lib/52_human_in_loop/)),\nand delivery/meta ([Genericity\nMatrix](../lib/92_genericity_matrix/), [Library\nRegistry](../lib/93_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 sample at `standards/src/templates/app/`\n (server) and `standards/src/templates/client/` (React admin); walkthrough in\n the [Reference App](06_reference_app/01_overview.md)\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": {}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
"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",
|
||||
"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/93_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": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"module": "wiki_reference_app_client_admin",
|
||||
"content": {
|
||||
"path": "wiki_reference_app_client_admin",
|
||||
"docstring": "# Client Admin — the spec-driven frontend\n\nThe client sample (`standards/src/templates/client/`) renders a full admin —\nauth pages, resource lists, forms, detail views — **from the server's\n`openapi.yaml`**, driven entirely by the x- extension fields. There are no\nhand-written screens per resource.\n\n## What ships\n\nA Vite + React + MUI app wiring the two Aetos packages:\n\n- **`@aetoskia/react-openapi`** — fetches `${VITE_API_BASE_URL}/openapi.json`,\n builds the router and the resource admin from the spec's schemas and x-\n fields (list columns, autocomplete, fk pickers, enum selects, hidden fields…)\n- **`@aetoskia/react-auth`** — login/register/logout against the auth-server\n paths declared in the spec's `securitySchemes.bearerAuth`, and attaches the\n JWT to every API call\n- a shared MUI theme (`src/shared-theme/`) and the shell\n (`Header` / `Footer` / `Home`)\n\n`src/openapi-config.ts` is the only configuration:\n\n```ts\nexport const specConfiguration: SpecConfiguration = {\n specUrl: `${apiBase}/openapi.json`,\n baseApiUrl: apiBase,\n title: \"Vet Admin\",\n getToken: () => tokenStore.get(),\n};\n```\n\n## Pair it with the sample server\n\n```bash\ncd apps/vet-sample-client\nprintf 'VITE_API_BASE_URL=http://localhost:8020\\nVITE_AUTH_BASE_URL=http://localhost:8020\\n' > .env.local\nnpm install\nnpm run dev\n```\n\nOpen the dev-server URL; the admin lists Parents, Pets, Treatments, Vets,\nAppointments and Jobs straight from the vet spec. Edit `openapi.yaml` on the\nserver and the client follows on reload — the spec is the contract on both\nsides.\n\n## Where the x- extensions come from\n\nThe canonical catalog lives in the react-openapi package\n(`spec-extensions.ts`, served to agents as the `specExtensions` module of the\nreact-openapi MCP bundle). The worked examples on the server side are the\n`Job` schema (enum selects via `x-edit-as: select` + `x-ui-type: enum`,\n`x-hidden` field masks, `x-list-columns`) and the FK fields on `Appointment`\n(`x-fk: {resource: pets, prefetch: true}`).\n\n## CI\n\nThe client ships a `drone-npm` shape (Type B in\n[Drone Pipelines](../lib/90_drone_pipelines/)) and follows the\n[Versioning](../lib/91_versioning/) standard (`package.json` +\n`CHANGELOG.md`).\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"module": "wiki_reference_app_domain_and_api",
|
||||
"content": {
|
||||
"path": "wiki_reference_app_domain_and_api",
|
||||
"docstring": "# Domain & API — the resource axis\n\nEvery aggregate follows the same four-file slice\n([CRUD Template](../lib/21_crud_template/)); walk `parents` first, then\n`appointments` when you need foreign keys.\n\n## The slice, end to end (parents)\n\n```\ndomain/parents/\n models.py # framework-free Pydantic model (id optional)\n ports/\n db.py # ParentDocument(Parent, BaseDocument) + create_parent_repo()\n service.py # ParentCrudsService(CrudsServiceAdaptor[...]) + get_parent_service()\ninterfaces/parents/\n routes.py # list/create/get/update/delete — names == operationIds\n```\n\nWiring happens in exactly one place — the composition root\n`init.py`:\n\n```python\nparent_repo = create_parent_repo()\n_resources[\"parent_svc\"] = get_parent_service(parent_repo)\n```\n\nRoutes resolve the service from app state through the shared helper\n(`interfaces/_common.py`):\n\n```python\nasync def list_parents(request: Request, limit: int = 20, offset: int = 0) -> dict:\n docs = await svc(request, \"parent\").search({})\n return paginate([dump(d) for d in docs], limit, offset)\n```\n\n## The spec is the contract\n\n`openapi.yaml` is OpenAPI 3.1 and the\nsingle source of truth: `main.py` builds an `OpenAPIFirstApp` that binds each\n`operationId` to a function of the same name in `interfaces/openapi.py` (a flat\nre-export of every routes module). There are no decorator routers.\n\nThe x- extension fields on schemas and properties are what make the admin\nclient render itself — see [Auto-Rendered Admin](../lib/40_ui_admin/) and\nthe worked `Job` schema (enum selects, hidden fields, filterable columns).\n\n## Foreign keys (appointments)\n\n`Appointment` declares `pet`, `vet`, `treatment` as `dict | int | None`. The\nwrite path normalizes bare ids through `expand_ref`; the read path returns\nfully expanded objects:\n\n```python\npet = await expand_ref(svc(request, \"pet\"), body.get(\"pet\"), \"pet\")\n```\n\nThe list endpoint additionally filters by date prefix and embedded fk ids —\nclient-side over the repository result, the pattern for small reference data.\n\n## Ids on the wire\n\nThe domain models declare `id: int | None = None` and documents use the\n**integer-id** repository variants (`infra/db/repos/mongo_int.py`,\n`create_repo()` in `domain/*/ports/db.py`, `REPO_BACKEND=memory|mongo`). This\nkeeps the OpenAPI `id: integer` contract, the in-memory demo store, and the\nMongo persistence layer consistent.\n\n## Tests\n\n- `tests/integration/test_openapi_app_builds.py` — generic gate: every\n `operationId` resolves and the app builds. Copy it verbatim.\n- `tests/unit/test_jobs_routes.py` — the pattern for route unit tests with a\n service double (no HTTP client needed).\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
8
standards/mcp/modules/wiki_reference_app_overview.json
Normal file
8
standards/mcp/modules/wiki_reference_app_overview.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"module": "wiki_reference_app_overview",
|
||||
"content": {
|
||||
"path": "wiki_reference_app_overview",
|
||||
"docstring": "# Reference App — Overview\n\nThe standards ship a **runnable sample** of the whole blueprint: the **vet**\nveterinary-clinic application. It exists to be copied — it is the reference\nfor how a real domain sits on the architecture, and the code an agent pulls\nthrough the MCP tools (`get_app_template`, `get_app_component`).\n\n!!! note \"One sample, two templates\"\n The **server** sample lives at `standards/src/templates/app/`\n (FastAPI + openapi-first + mongo-ops + hexa). The **client** sample lives\n at `standards/src/templates/client/` (Vite + MUI + `@aetoskia/react-openapi`\n + `@aetoskia/react-auth`). Together they are the full-stack reference.\n\n---\n\n## The domain at a glance\n\n| Aggregate | What it demonstrates |\n|---------------|---------------------------------------------------------------|\n| **parents** | The simplest CRUD slice (no foreign keys) — copy this first |\n| **pets** | Embedded list-of-refs (`parents[]`), file upload, enum fields |\n| **treatments**| Plain resource with pricing fields |\n| **vets** | Plain resource; target of prefetch FKs |\n| **appointments** | Single FKs (`pet`, `vet`, `treatment`) expanded on read, filtered list |\n| **jobs** | The worker's queue — durable status machine |\n\nThe pipeline axis ships one worked family, **`appointment_reminder`**, plus the\nreusable `pipelines/shared/` scaffold and a `worker.py` that dispatches jobs by\n`job.kind`.\n\n## Run it\n\n```bash\ncp -r <docs>/standards/src/templates/app apps/vet-sample\ncd apps/vet-sample\npip install -e \".[dev]\"\n\nuvicorn main:app --reload # API on :8020 (memory backend, zero services)\npython worker.py # background worker (polls jobs)\npython -m scripts.seed # optional: seed demo data (mongo backend)\n```\n\nThe default `REPO_BACKEND=memory` means the API boots with **no external\nservices** — repositories are process-local dictionaries. Switch to\n`REPO_BACKEND=mongo` for persistence (see `.env.example`).\n\nThe client pairs with it:\n\n```bash\ncp -r <docs>/standards/src/templates/client apps/vet-sample-client\ncd apps/vet-sample-client\necho \"VITE_API_BASE_URL=http://localhost:8020\" > .env.local\necho \"VITE_AUTH_BASE_URL=http://localhost:8020\" >> .env.local\nnpm install\nnpm run dev # Vite dev server\n```\n\n!!! warning \"Auth\"\n The server ships with the `bearerAuth` security scheme declared\n (`{AUTH_SERVER}` introspection). Without a reachable auth-server the API\n answers `401` on resource routes; `/health` stays public. For a local\n no-auth demo, remove the `security` block from `openapi.yaml` and the\n global security entry — see\n [Auth Integration](../lib/22_auth_integration/).\n\n## Next\n\n- [Domain & API](02_domain_and_api.md) — walking the resource axis\n- [Pipelines & Worker](03_pipelines_and_worker.md) — the pipeline axis\n- [Client Admin](04_client_admin.md) — the spec-driven frontend\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"module": "wiki_reference_app_pipelines_and_worker",
|
||||
"content": {
|
||||
"path": "wiki_reference_app_pipelines_and_worker",
|
||||
"docstring": "# Pipelines & Worker — the pipeline axis\n\nThe sample proves the full loop: **API creates a job document → worker polls →\npipeline family runs → job reaches a terminal status.**\n\n## The jobs slice\n\n`domain/jobs/` is an ordinary CRUD slice; its model is the durable state\nmachine the worker drives:\n\n```python\nclass Job(BaseModel):\n name: str\n kind: str # dispatch key\n status: Literal[\"pending\", \"processing\", \"done\", \"failed\"] = \"pending\"\n payload: dict | None = None\n retry_count: int = 0\n error_message: str | None = None\n```\n\n`POST /jobs` with `{\"name\": \"...\", \"kind\": \"appointment_reminder\",\n\"payload\": {\"appointment_id\": 1}}` enqueues work; `GET /jobs?status=...`\ninspects the queue.\n\n## The appointment_reminder family\n\n```\npipelines/appointment_reminder/\n abc.py # LoadPort / ComposePort / DeliverPort + root interface\n impl.py # concrete stages, wired root, register(\"appointment_reminder\", ...)\n pipeline.yaml # graph spec (nodes, edges, roots)\n resources/\n pipeline.yaml # runtime spec consumed by the loader\n patterns/reminder.yaml # message template fragments\npipelines/shared/\n ports.py loader.py registry.py # reused by every family\n```\n\nEach stage is a small, independently testable unit. The loader\n(`pipelines/shared/loader.py`) wires the root through `hexa.build` and merges\n`resources/patterns/*.yaml` into the stage-injected registry:\n\n```python\npipeline = get_appointment_reminder_pipeline(appointment_svc)\nreminder = await pipeline.run(appointment_id)\n```\n\nTo add a family: copy `appointment_reminder/`, replace the stages, call\n`register(kind, variant, root_cls)` — see\n[Pipeline Scaffold](../lib/50_pipeline_scaffold/).\n\n## The worker\n\n`worker.py` is the process that turns\ndocuments into execution:\n\n1. poll `job_svc.search({\"status\": \"pending\"})` on `WORKER_POLL_SECONDS`\n2. claim a job: `status → processing`\n3. dispatch through the `HANDLERS` table — `HANDLERS[job.kind](job, resources)`\n4. on success `status → done`; on exception `status → failed`,\n `retry_count += 1`, `error_message` captured; jobs at\n `WORKER_MAX_RETRIES` are skipped\n\n```python\nHANDLERS: dict[str, Callable] = {\n \"appointment_reminder\": run_appointment_reminder,\n # add yours here\n}\n```\n\nThe worker shares the composition root: `from init import init` gives it the\nsame wired services the API uses — one place to add a dependency.\n\n!!! note \"Same process model in production\"\n API and worker are separate processes (see `docker-compose.yaml`: `api`,\n `worker`, `mongo`). Any in-memory progress bus is per-process; to surface\n live progress through the API, swap the bus adaptor for a broker-backed\n one — see [Worker & Progress](../lib/51_worker_and_progress/).\n\n## Tests\n\n- `tests/unit/test_appointment_reminder_pipeline.py` — stage units + the wired\n root against a fake service.\n- `tests/unit/test_worker.py` — dispatch, done/failed transitions, retry\n exhaustion, using an in-memory job service double and a cancelled poll task.\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"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",
|
||||
"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/20_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)`, authoring only the 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/92_genericity_matrix/) — Tier A / B / C\n- [Library Registry](../lib/93_library_registry/) — the Tier A catalog\n- [App Components](02_components.md) — the component model\n- [Reference App](06_reference_app/01_overview.md) — the runnable vet sample to copy\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": {}
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,16 @@
|
||||
"resource": "docs://modules/build_recipe",
|
||||
"title": "Build Recipe"
|
||||
},
|
||||
{
|
||||
"module": "layering_rules",
|
||||
"resource": "docs://modules/layering_rules",
|
||||
"title": "Layering Rules"
|
||||
},
|
||||
{
|
||||
"module": "composition_root",
|
||||
"resource": "docs://modules/composition_root",
|
||||
"title": "Composition Root"
|
||||
},
|
||||
{
|
||||
"module": "layering_rules",
|
||||
"resource": "docs://modules/layering_rules",
|
||||
"title": "Layering Rules"
|
||||
},
|
||||
{
|
||||
"module": "spec_first_routing",
|
||||
"resource": "docs://modules/spec_first_routing",
|
||||
@@ -24,6 +24,21 @@
|
||||
"resource": "docs://modules/crud_template",
|
||||
"title": "CRUD Template"
|
||||
},
|
||||
{
|
||||
"module": "auth_integration",
|
||||
"resource": "docs://modules/auth_integration",
|
||||
"title": "Auth Integration"
|
||||
},
|
||||
{
|
||||
"module": "infra_adaptors",
|
||||
"resource": "docs://modules/infra_adaptors",
|
||||
"title": "Infra Adapters"
|
||||
},
|
||||
{
|
||||
"module": "ui_admin",
|
||||
"resource": "docs://modules/ui_admin",
|
||||
"title": "UI: Auto-Rendered Admin & Custom Pages"
|
||||
},
|
||||
{
|
||||
"module": "pipeline_scaffold",
|
||||
"resource": "docs://modules/pipeline_scaffold",
|
||||
@@ -39,11 +54,6 @@
|
||||
"resource": "docs://modules/human_in_loop",
|
||||
"title": "Human-in-the-Loop"
|
||||
},
|
||||
{
|
||||
"module": "infra_adaptors",
|
||||
"resource": "docs://modules/infra_adaptors",
|
||||
"title": "Infra Adapters"
|
||||
},
|
||||
{
|
||||
"module": "drone_pipelines",
|
||||
"resource": "docs://modules/drone_pipelines",
|
||||
@@ -64,11 +74,6 @@
|
||||
"resource": "docs://modules/library_registry",
|
||||
"title": "Library Registry"
|
||||
},
|
||||
{
|
||||
"module": "auth_integration",
|
||||
"resource": "docs://modules/auth_integration",
|
||||
"title": "Auth Integration"
|
||||
},
|
||||
{
|
||||
"module": "repository_inventory",
|
||||
"resource": "docs://modules/repository_inventory",
|
||||
@@ -109,6 +114,26 @@
|
||||
"resource": "docs://modules/wiki_mcp_setup",
|
||||
"title": "🔌 MCP Setup"
|
||||
},
|
||||
{
|
||||
"module": "wiki_reference_app_overview",
|
||||
"resource": "docs://modules/wiki_reference_app_overview",
|
||||
"title": "Overview"
|
||||
},
|
||||
{
|
||||
"module": "wiki_reference_app_domain_and_api",
|
||||
"resource": "docs://modules/wiki_reference_app_domain_and_api",
|
||||
"title": "Domain And Api"
|
||||
},
|
||||
{
|
||||
"module": "wiki_reference_app_pipelines_and_worker",
|
||||
"resource": "docs://modules/wiki_reference_app_pipelines_and_worker",
|
||||
"title": "Pipelines And Worker"
|
||||
},
|
||||
{
|
||||
"module": "wiki_reference_app_client_admin",
|
||||
"resource": "docs://modules/wiki_reference_app_client_admin",
|
||||
"title": "Client Admin"
|
||||
},
|
||||
{
|
||||
"module": "wiki_index",
|
||||
"resource": "docs://modules/wiki_index",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"note": "Machine-readable repository inventory. Mirrors standards/src/lib/94_repository_inventory.md. Read it with the registry://repos resource or the list_repos tool.",
|
||||
"path_convention": "relative to the aetos/ workspace root",
|
||||
"repos": [
|
||||
{
|
||||
@@ -35,6 +35,22 @@
|
||||
"port": 8001
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "doc-weaver",
|
||||
"type": "package",
|
||||
"path": "react/doc-weaver",
|
||||
"version_source": "package.json",
|
||||
"drone": "npm-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": [
|
||||
"lib",
|
||||
"wiki"
|
||||
],
|
||||
"mcp": {
|
||||
"server": "doc-weaver",
|
||||
"port": 8010
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hexa",
|
||||
"type": "package",
|
||||
@@ -131,6 +147,38 @@
|
||||
"port": 8005
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "react-auth",
|
||||
"type": "package",
|
||||
"path": "react/react-auth",
|
||||
"version_source": "package.json",
|
||||
"drone": "npm-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": [
|
||||
"lib",
|
||||
"wiki"
|
||||
],
|
||||
"mcp": {
|
||||
"server": "react-auth",
|
||||
"port": 8011
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "react-openapi",
|
||||
"type": "package",
|
||||
"path": "react/react-openapi",
|
||||
"version_source": "package.json",
|
||||
"drone": "npm-lib.yaml",
|
||||
"trigger": "tag",
|
||||
"docs": [
|
||||
"lib",
|
||||
"wiki"
|
||||
],
|
||||
"mcp": {
|
||||
"server": "react-openapi",
|
||||
"port": 8012
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "auth-server",
|
||||
"type": "service",
|
||||
|
||||
@@ -5,4 +5,8 @@ __pycache__/
|
||||
.git/
|
||||
docs/
|
||||
dist/
|
||||
build/
|
||||
build/
|
||||
uploads/
|
||||
_gen/
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
DEBUG=false
|
||||
REPO_BACKEND=memory
|
||||
MONGO_URI=mongodb://localhost:27017
|
||||
MONGO_DB=app_template
|
||||
WORKER_POLL_SECONDS=30
|
||||
WORKER_MAX_RETRIES=3
|
||||
AUTH_SERVER=http://localhost:8000
|
||||
JWT_SERVER=http://localhost:8000
|
||||
MONGO_DB=vet
|
||||
UPLOADS_DIR=uploads
|
||||
PORT=8020
|
||||
AUTH_SERVER=https://auth.aetoskia.com
|
||||
WORKER_POLL_SECONDS=5
|
||||
WORKER_MAX_RETRIES=3
|
||||
3
standards/mcp/templates/app/.gitignore
vendored
3
standards/mcp/templates/app/.gitignore
vendored
@@ -15,6 +15,9 @@ venv/
|
||||
# Generated wire contract (regenerate from openapi.yaml)
|
||||
_gen/
|
||||
|
||||
# Uploads
|
||||
uploads/
|
||||
|
||||
# Packaging / coverage
|
||||
dist/
|
||||
build/
|
||||
|
||||
@@ -12,9 +12,7 @@ COPY . .
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir --extra-index-url "${PIP_EXTRA_INDEX_URL}" .
|
||||
|
||||
EXPOSE 8000
|
||||
EXPOSE 8020
|
||||
|
||||
# Two processes, per the two axes:
|
||||
# - main.py : the spec-first FastAPI application
|
||||
# - worker.py : the pipeline consumer polling pending jobs
|
||||
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port 8000 & exec python worker.py"]
|
||||
# Spec-first FastAPI application (no worker by design for this service)
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8020"]
|
||||
1
standards/mcp/templates/app/VERSION
Normal file
1
standards/mcp/templates/app/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
0.1.0
|
||||
@@ -3,6 +3,25 @@
|
||||
App configuration loaded from environment — a Settings dataclass with defaults
|
||||
plus a singleton settings instance populated from OS environment variables at
|
||||
import time.
|
||||
|
||||
---
|
||||
|
||||
The server reads six knobs:
|
||||
|
||||
- ``repo_backend`` — ``memory`` (default, zero-dependency demo store) or
|
||||
``mongo`` (persistent, via the shared mongo-ops connection manager).
|
||||
- ``mongo_uri`` / ``mongo_db`` — connection targets when the Mongo backend is
|
||||
selected.
|
||||
- ``uploads_dir`` — directory that receives uploaded pet photos.
|
||||
- ``port`` — the port uvicorn binds (kept in sync with the OpenAPI
|
||||
``servers:`` entry and the frontend's ``VITE_API_BASE_URL``).
|
||||
- ``worker_poll_seconds`` — background worker poll cadence.
|
||||
- ``worker_max_retries`` — failed-attempt budget before a job is skipped.
|
||||
|
||||
``AUTH_SERVER`` is intentionally *not* re-exported here: ``openapi_first``
|
||||
resolves ``{AUTH_SERVER}`` straight from the process environment when the
|
||||
application is built, so the env var remains the single source of truth for
|
||||
the generated bearer-JWT introspection dependency.
|
||||
"""
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
@@ -14,23 +33,38 @@ class Settings:
|
||||
|
||||
Attributes:
|
||||
debug (bool): Enable debug behavior. Defaults to False.
|
||||
repo_backend (str): Persistence flavour, ``memory`` or ``mongo``.
|
||||
Defaults to ``memory``.
|
||||
mongo_uri (str): MongoDB connection URI. Defaults to mongodb://localhost:27017.
|
||||
mongo_db (str): MongoDB database name. Defaults to app_template.
|
||||
worker_poll_seconds (int): Worker poll interval in seconds. Defaults to 30.
|
||||
worker_max_retries (int): Maximum job retries before skipping. Defaults to 3.
|
||||
mongo_db (str): MongoDB database name. Defaults to vet.
|
||||
auth_server (str): Auth-server base URL surfaced to operators and
|
||||
scripts. Defaults to https://auth.aetoskia.com.
|
||||
port (int): Local HTTP port. Defaults to 8020.
|
||||
uploads_dir (str): Directory for uploaded pet photos. Defaults to uploads.
|
||||
worker_poll_seconds (float): Worker poll cadence in seconds. Defaults to 5.
|
||||
worker_max_retries (int): Failed attempts before a job is skipped.
|
||||
Defaults to 3.
|
||||
"""
|
||||
|
||||
debug: bool = False
|
||||
repo_backend: str = "memory"
|
||||
mongo_uri: str = "mongodb://localhost:27017"
|
||||
mongo_db: str = "app_template"
|
||||
worker_poll_seconds: int = 30
|
||||
mongo_db: str = "vet"
|
||||
auth_server: str = "https://auth.aetoskia.com"
|
||||
port: int = 8020
|
||||
uploads_dir: str = "uploads"
|
||||
worker_poll_seconds: float = 5.0
|
||||
worker_max_retries: int = 3
|
||||
|
||||
|
||||
settings = Settings(
|
||||
debug=os.environ.get("DEBUG", "").lower() in ("1", "true", "yes"),
|
||||
repo_backend=os.environ.get("REPO_BACKEND", "memory").lower(),
|
||||
mongo_uri=os.environ.get("MONGO_URI", "mongodb://localhost:27017"),
|
||||
mongo_db=os.environ.get("MONGO_DB", "app_template"),
|
||||
worker_poll_seconds=int(os.environ.get("WORKER_POLL_SECONDS", "30")),
|
||||
mongo_db=os.environ.get("MONGO_DB", "vet"),
|
||||
auth_server=os.environ.get("AUTH_SERVER", "https://auth.aetoskia.com"),
|
||||
port=int(os.environ.get("PORT", "8020")),
|
||||
uploads_dir=os.environ.get("UPLOADS_DIR", "uploads"),
|
||||
worker_poll_seconds=float(os.environ.get("WORKER_POLL_SECONDS", "5")),
|
||||
worker_max_retries=int(os.environ.get("WORKER_MAX_RETRIES", "3")),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
services:
|
||||
api:
|
||||
build: .
|
||||
command: uvicorn main:app --host 0.0.0.0 --port 8000
|
||||
command: uvicorn main:app --host 0.0.0.0 --port 8020
|
||||
env_file: .env
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- "8020:8020"
|
||||
depends_on:
|
||||
- mongo
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Appointments domain slice."""
|
||||
68
standards/mcp/templates/app/domain/appointments/models.py
Normal file
68
standards/mcp/templates/app/domain/appointments/models.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""# Summary
|
||||
|
||||
Appointments domain — scheduled visits linking a pet, a vet and a treatment.
|
||||
"""
|
||||
from pydantic import BaseModel
|
||||
|
||||
from domain.parents.models import Metadata
|
||||
|
||||
|
||||
class ProcedureNote(BaseModel):
|
||||
"""A single procedure note; ``noteType`` selects the shape.
|
||||
|
||||
Attributes:
|
||||
noteType (str): Discriminator (basic, heart_rate, dental, vaccine,
|
||||
preop, surgery).
|
||||
summary (str | None): Short summary. Defaults to None.
|
||||
details (str | None): Longer details. Defaults to None.
|
||||
"""
|
||||
|
||||
noteType: str
|
||||
summary: str | None = None
|
||||
details: str | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
|
||||
|
||||
class Procedure(BaseModel):
|
||||
"""A procedure performed during an appointment.
|
||||
|
||||
Attributes:
|
||||
name (str): Procedure name.
|
||||
description (str | None): Procedure description. Defaults to None.
|
||||
cost (float | None): Cost of the procedure. Defaults to None.
|
||||
notes (ProcedureNote | None): Typed procedure note. Defaults to None.
|
||||
"""
|
||||
|
||||
name: str
|
||||
description: str | None = None
|
||||
cost: float | None = None
|
||||
notes: ProcedureNote | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
|
||||
|
||||
class Appointment(BaseModel):
|
||||
"""A scheduled appointment.
|
||||
|
||||
Attributes:
|
||||
id (int | None): Server-assigned identifier. Defaults to None.
|
||||
date (str): ISO date-time of the appointment.
|
||||
notes (str | None): Additional notes. Defaults to None.
|
||||
procedures (list[Procedure]): Procedures performed. Defaults to empty.
|
||||
pet (dict | int | None): Embedded pet object or pet id.
|
||||
vet (dict | int | None): Embedded vet object or vet id.
|
||||
treatment (dict | int | None): Embedded treatment or treatment id.
|
||||
metadata (Metadata | None): Presentation metadata. Defaults to None.
|
||||
"""
|
||||
|
||||
id: int | None = None
|
||||
date: str
|
||||
notes: str | None = None
|
||||
procedures: list[Procedure] = []
|
||||
pet: dict | int | None = None
|
||||
vet: dict | int | None = None
|
||||
treatment: dict | int | None = None
|
||||
metadata: Metadata | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Appointments domain ports."""
|
||||
25
standards/mcp/templates/app/domain/appointments/ports/db.py
Normal file
25
standards/mcp/templates/app/domain/appointments/ports/db.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""# Summary
|
||||
|
||||
Appointments persistence ports — integer-id document and repository factory.
|
||||
"""
|
||||
from mongo_ops import BaseDocument
|
||||
from pydantic import Field
|
||||
|
||||
from domain.appointments.models import Appointment
|
||||
from infra.db.repos import create_repo
|
||||
|
||||
|
||||
class AppointmentDocument(Appointment, BaseDocument):
|
||||
"""Persisted appointment document. The integer ``id`` shadows the ObjectId."""
|
||||
|
||||
id: int | None = Field(default=None, validation_alias="_id")
|
||||
|
||||
|
||||
def create_appointment_repo():
|
||||
"""Create the appointment repository selected by ``settings.repo_backend``.
|
||||
|
||||
Returns:
|
||||
IntIdMemoryRepository | IntIdMongoRepository: Repository wired to the
|
||||
``appointments`` collection/store.
|
||||
"""
|
||||
return create_repo("appointments", AppointmentDocument)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""# Summary
|
||||
|
||||
CRUD service wiring for the appointment aggregate via the hexa container.
|
||||
"""
|
||||
from hexa import build
|
||||
|
||||
from domain.appointments.models import Appointment
|
||||
from domain.appointments.ports.db import AppointmentDocument
|
||||
from infra.adapters.service import CrudsServiceAdaptor
|
||||
|
||||
|
||||
class AppointmentCrudsService(CrudsServiceAdaptor[Appointment, AppointmentDocument]):
|
||||
"""Appointment CRUD service resolving :class:`AppointmentDocument` from a repo.
|
||||
|
||||
Notes:
|
||||
Instances are created by :func:`get_appointment_service`. The ``model``
|
||||
and ``schema`` class attributes bind the domain model and its document.
|
||||
"""
|
||||
|
||||
model = Appointment
|
||||
schema = AppointmentDocument
|
||||
|
||||
|
||||
def get_appointment_service(repo) -> AppointmentCrudsService:
|
||||
"""Build an appointment CRUD service wired to ``repo``.
|
||||
|
||||
Args:
|
||||
repo (Any): Integer-id appointment repository from
|
||||
:func:`create_appointment_repo`.
|
||||
|
||||
Returns:
|
||||
AppointmentCrudsService: Ready-to-use appointment CRUD service.
|
||||
"""
|
||||
return build(AppointmentCrudsService, instances={"repo": repo})
|
||||
@@ -1,19 +0,0 @@
|
||||
"""# Summary
|
||||
|
||||
Item domain model — the neutral resource used throughout the template.
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
"""A single countable resource.
|
||||
|
||||
Attributes:
|
||||
name (str): Item name.
|
||||
quantity (int): Item quantity. Defaults to 1.
|
||||
"""
|
||||
|
||||
name: str
|
||||
quantity: int = Field(default=1)
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
@@ -1,18 +0,0 @@
|
||||
"""# Summary
|
||||
|
||||
Optional converters for the item slice — pure functions mapping between domain
|
||||
types. Add a converter here when a service must reshape data; keep it pure.
|
||||
"""
|
||||
from domain.item.models import Item
|
||||
|
||||
|
||||
def to_item(data: dict) -> Item:
|
||||
"""Build an :class:`Item` from a raw mapping.
|
||||
|
||||
Args:
|
||||
data (dict): Field mapping matching the item model.
|
||||
|
||||
Returns:
|
||||
Item: The constructed item model.
|
||||
"""
|
||||
return Item(**data)
|
||||
@@ -1,21 +0,0 @@
|
||||
"""# Summary
|
||||
|
||||
Mongo persistence for items: document schema and repository factory.
|
||||
"""
|
||||
from mongo_ops import BaseDocument, BaseRepository
|
||||
|
||||
from domain.item.models import Item
|
||||
from infra.db.repos.mongo import _repo_factory as mongo_repo_factory
|
||||
|
||||
|
||||
class ItemDocument(Item, BaseDocument):
|
||||
"""Mongo document form of :class:`Item`."""
|
||||
|
||||
|
||||
def create_item_repo() -> BaseRepository:
|
||||
"""Create the Mongo-backed item repository.
|
||||
|
||||
Returns:
|
||||
BaseRepository: Repository wired to the ``items`` collection.
|
||||
"""
|
||||
return mongo_repo_factory("items", ItemDocument)
|
||||
@@ -1,33 +0,0 @@
|
||||
"""# Summary
|
||||
|
||||
CRUD service wiring for the item aggregate via the hexa container.
|
||||
"""
|
||||
from hexa import build
|
||||
|
||||
from domain.item.models import Item
|
||||
from domain.item.ports.db import ItemDocument
|
||||
from infra.adapters.service import CrudsServiceAdaptor
|
||||
|
||||
|
||||
class ItemCrudsService(CrudsServiceAdaptor[Item, ItemDocument]):
|
||||
"""Item CRUD service resolving :class:`ItemDocument` from a repo.
|
||||
|
||||
Notes:
|
||||
Instances are created by :func:`get_item_service`. The ``model`` and
|
||||
``schema`` class attributes bind the domain model and its document type.
|
||||
"""
|
||||
|
||||
model = Item
|
||||
schema = ItemDocument
|
||||
|
||||
|
||||
def get_item_service(repo) -> ItemCrudsService:
|
||||
"""Build an item CRUD service wired to ``repo``.
|
||||
|
||||
Args:
|
||||
repo (Any): Mongo-backed item repository from :func:`create_item_repo`.
|
||||
|
||||
Returns:
|
||||
ItemCrudsService: Ready-to-use item CRUD service.
|
||||
"""
|
||||
return build(ItemCrudsService, instances={"repo": repo})
|
||||
@@ -1,27 +0,0 @@
|
||||
"""# Summary
|
||||
|
||||
Job domain model — a pipeline work item consumed by the background worker.
|
||||
"""
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Job(BaseModel):
|
||||
"""A unit of background work.
|
||||
|
||||
Attributes:
|
||||
name (str): Job name.
|
||||
status (str): Lifecycle status. Defaults to "pending".
|
||||
retry_count (int): Times the job failed. Defaults to 0.
|
||||
error_message (str | None): Last failure message. Defaults to None.
|
||||
payload (dict | None): Arbitrary job payload. Defaults to None.
|
||||
"""
|
||||
|
||||
name: str
|
||||
status: Literal["pending", "processing", "done", "failed"] = "pending"
|
||||
retry_count: int = Field(default=0)
|
||||
error_message: str | None = None
|
||||
payload: dict | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
@@ -1,21 +0,0 @@
|
||||
"""# Summary
|
||||
|
||||
Mongo persistence for jobs: document schema and repository factory.
|
||||
"""
|
||||
from mongo_ops import BaseDocument, BaseRepository
|
||||
|
||||
from domain.job.models import Job
|
||||
from infra.db.repos.mongo import _repo_factory as mongo_repo_factory
|
||||
|
||||
|
||||
class JobDocument(Job, BaseDocument):
|
||||
"""Mongo document form of :class:`Job`."""
|
||||
|
||||
|
||||
def create_job_repo() -> BaseRepository:
|
||||
"""Create the Mongo-backed job repository.
|
||||
|
||||
Returns:
|
||||
BaseRepository: Repository wired to the ``jobs`` collection.
|
||||
"""
|
||||
return mongo_repo_factory("jobs", JobDocument)
|
||||
37
standards/mcp/templates/app/domain/jobs/models.py
Normal file
37
standards/mcp/templates/app/domain/jobs/models.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""# Summary
|
||||
|
||||
Jobs domain — the worker's queue. Each job is a unit of pipeline work the
|
||||
background worker claims and executes.
|
||||
"""
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from domain.parents.models import Metadata
|
||||
|
||||
|
||||
class Job(BaseModel):
|
||||
"""A unit of background work.
|
||||
|
||||
Attributes:
|
||||
id (int | None): Server-assigned identifier. Defaults to None.
|
||||
name (str): Human label for the job.
|
||||
kind (str): Dispatch key — selects the pipeline (e.g.
|
||||
``appointment_reminder``).
|
||||
status (str): Lifecycle status. Defaults to ``pending``.
|
||||
payload (dict | None): Job-specific input. Defaults to None.
|
||||
retry_count (int): Failed attempts so far. Defaults to 0.
|
||||
error_message (str | None): Last failure detail. Defaults to None.
|
||||
metadata (Metadata | None): Presentation metadata. Defaults to None.
|
||||
"""
|
||||
|
||||
id: int | None = None
|
||||
name: str
|
||||
kind: str
|
||||
status: Literal["pending", "processing", "done", "failed"] = "pending"
|
||||
payload: dict | None = None
|
||||
retry_count: int = 0
|
||||
error_message: str | None = None
|
||||
metadata: Metadata | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
27
standards/mcp/templates/app/domain/jobs/ports/db.py
Normal file
27
standards/mcp/templates/app/domain/jobs/ports/db.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""# Summary
|
||||
|
||||
Jobs persistence ports — integer-id document and repository factory.
|
||||
"""
|
||||
from mongo_ops import BaseDocument
|
||||
from pydantic import Field
|
||||
|
||||
from config import settings
|
||||
from domain.jobs.models import Job
|
||||
from infra.db.repos import create_repo
|
||||
|
||||
|
||||
class JobDocument(Job, BaseDocument):
|
||||
"""Persisted job document. The integer ``id`` shadows the ObjectId."""
|
||||
|
||||
id: int | None = Field(default=None, validation_alias="_id")
|
||||
|
||||
|
||||
def create_job_repo():
|
||||
"""Create the job repository selected by ``settings.repo_backend``.
|
||||
|
||||
Returns:
|
||||
IntIdMemoryRepository | IntIdMongoRepository: Repository wired to the
|
||||
``jobs`` collection/store.
|
||||
"""
|
||||
_ = settings # backend chosen centrally by create_repo
|
||||
return create_repo("jobs", JobDocument)
|
||||
@@ -4,8 +4,8 @@ CRUD service wiring for the job aggregate via the hexa container.
|
||||
"""
|
||||
from hexa import build
|
||||
|
||||
from domain.job.models import Job
|
||||
from domain.job.ports.db import JobDocument
|
||||
from domain.jobs.models import Job
|
||||
from domain.jobs.ports.db import JobDocument
|
||||
from infra.adapters.service import CrudsServiceAdaptor
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ def get_job_service(repo) -> JobCrudsService:
|
||||
"""Build a job CRUD service wired to ``repo``.
|
||||
|
||||
Args:
|
||||
repo (Any): Mongo-backed job repository from :func:`create_job_repo`.
|
||||
repo (Any): Integer-id job repository from :func:`create_job_repo`.
|
||||
|
||||
Returns:
|
||||
JobCrudsService: Ready-to-use job CRUD service.
|
||||
"""
|
||||
return build(JobCrudsService, instances={"repo": repo})
|
||||
return build(JobCrudsService, instances={"repo": repo})
|
||||
3
standards/mcp/templates/app/domain/parents/__init__.py
Normal file
3
standards/mcp/templates/app/domain/parents/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Parents domain slice."""
|
||||
39
standards/mcp/templates/app/domain/parents/models.py
Normal file
39
standards/mcp/templates/app/domain/parents/models.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""# Summary
|
||||
|
||||
Parents domain — pet owners referenced by :class:`domain.pets.models.Pet`.
|
||||
"""
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Metadata(BaseModel):
|
||||
"""Optional presentation metadata carried alongside a resource.
|
||||
|
||||
Attributes:
|
||||
createdOn (str | None): ISO creation timestamp. Defaults to None.
|
||||
updatedOn (str | None): ISO update timestamp. Defaults to None.
|
||||
"""
|
||||
|
||||
createdOn: str | None = None
|
||||
updatedOn: str | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
|
||||
|
||||
class Parent(BaseModel):
|
||||
"""A pet owner.
|
||||
|
||||
Attributes:
|
||||
id (int | None): Server-assigned identifier. Defaults to None.
|
||||
name (str): Parent's full name.
|
||||
email (str): Email address.
|
||||
phone (str | None): Contact phone number. Defaults to None.
|
||||
metadata (Metadata | None): Presentation metadata. Defaults to None.
|
||||
"""
|
||||
|
||||
id: int | None = None
|
||||
name: str
|
||||
email: str
|
||||
phone: str | None = None
|
||||
metadata: Metadata | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Parents domain ports."""
|
||||
27
standards/mcp/templates/app/domain/parents/ports/db.py
Normal file
27
standards/mcp/templates/app/domain/parents/ports/db.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""# Summary
|
||||
|
||||
Parents persistence ports — integer-id document and repository factory.
|
||||
"""
|
||||
from mongo_ops import BaseDocument
|
||||
from pydantic import Field
|
||||
|
||||
from config import settings
|
||||
from domain.parents.models import Parent
|
||||
from infra.db.repos import create_repo
|
||||
|
||||
|
||||
class ParentDocument(Parent, BaseDocument):
|
||||
"""Persisted parent document. The integer ``id`` shadows the ObjectId."""
|
||||
|
||||
id: int | None = Field(default=None, validation_alias="_id")
|
||||
|
||||
|
||||
def create_parent_repo():
|
||||
"""Create the parent repository selected by ``settings.repo_backend``.
|
||||
|
||||
Returns:
|
||||
IntIdMemoryRepository | IntIdMongoRepository: Repository wired to the
|
||||
``parents`` collection/store.
|
||||
"""
|
||||
_ = settings # backend chosen centrally by create_repo
|
||||
return create_repo("parents", ParentDocument)
|
||||
33
standards/mcp/templates/app/domain/parents/ports/service.py
Normal file
33
standards/mcp/templates/app/domain/parents/ports/service.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""# Summary
|
||||
|
||||
CRUD service wiring for the parent aggregate via the hexa container.
|
||||
"""
|
||||
from hexa import build
|
||||
|
||||
from domain.parents.models import Parent
|
||||
from domain.parents.ports.db import ParentDocument
|
||||
from infra.adapters.service import CrudsServiceAdaptor
|
||||
|
||||
|
||||
class ParentCrudsService(CrudsServiceAdaptor[Parent, ParentDocument]):
|
||||
"""Parent CRUD service resolving :class:`ParentDocument` from a repo.
|
||||
|
||||
Notes:
|
||||
Instances are created by :func:`get_parent_service`. The ``model`` and
|
||||
``schema`` class attributes bind the domain model and its document type.
|
||||
"""
|
||||
|
||||
model = Parent
|
||||
schema = ParentDocument
|
||||
|
||||
|
||||
def get_parent_service(repo) -> ParentCrudsService:
|
||||
"""Build a parent CRUD service wired to ``repo``.
|
||||
|
||||
Args:
|
||||
repo (Any): Integer-id parent repository from :func:`create_parent_repo`.
|
||||
|
||||
Returns:
|
||||
ParentCrudsService: Ready-to-use parent CRUD service.
|
||||
"""
|
||||
return build(ParentCrudsService, instances={"repo": repo})
|
||||
3
standards/mcp/templates/app/domain/pets/__init__.py
Normal file
3
standards/mcp/templates/app/domain/pets/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Pets domain slice."""
|
||||
37
standards/mcp/templates/app/domain/pets/models.py
Normal file
37
standards/mcp/templates/app/domain/pets/models.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""# Summary
|
||||
|
||||
Pets domain — animals owned by parents and treated in appointments.
|
||||
"""
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from domain.parents.models import Metadata
|
||||
|
||||
|
||||
class Pet(BaseModel):
|
||||
"""A pet patient.
|
||||
|
||||
Attributes:
|
||||
id (int | None): Server-assigned identifier. Defaults to None.
|
||||
name (str): Name of the pet.
|
||||
species (str): Type of animal (dog, cat, bird).
|
||||
age (int | None): Age in years. Defaults to None.
|
||||
weight (float | None): Weight in kilograms. Defaults to None.
|
||||
birthDate (str | None): ISO date of birth. Defaults to None.
|
||||
photo (str | None): Stored photo path after upload. Defaults to None.
|
||||
parents (list[dict]): Embedded parent objects (or bare ids on input).
|
||||
metadata (Metadata | None): Presentation metadata. Defaults to None.
|
||||
"""
|
||||
|
||||
id: int | None = None
|
||||
name: str
|
||||
species: Literal["dog", "cat", "bird"]
|
||||
age: int | None = None
|
||||
weight: float | None = None
|
||||
birthDate: str | None = None
|
||||
photo: str | None = None
|
||||
parents: list[dict] = []
|
||||
metadata: Metadata | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Pets domain ports."""
|
||||
25
standards/mcp/templates/app/domain/pets/ports/db.py
Normal file
25
standards/mcp/templates/app/domain/pets/ports/db.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""# Summary
|
||||
|
||||
Pets persistence ports — integer-id document and repository factory.
|
||||
"""
|
||||
from mongo_ops import BaseDocument
|
||||
from pydantic import Field
|
||||
|
||||
from domain.pets.models import Pet
|
||||
from infra.db.repos import create_repo
|
||||
|
||||
|
||||
class PetDocument(Pet, BaseDocument):
|
||||
"""Persisted pet document. The integer ``id`` shadows the ObjectId."""
|
||||
|
||||
id: int | None = Field(default=None, validation_alias="_id")
|
||||
|
||||
|
||||
def create_pet_repo():
|
||||
"""Create the pet repository selected by ``settings.repo_backend``.
|
||||
|
||||
Returns:
|
||||
IntIdMemoryRepository | IntIdMongoRepository: Repository wired to the
|
||||
``pets`` collection/store.
|
||||
"""
|
||||
return create_repo("pets", PetDocument)
|
||||
33
standards/mcp/templates/app/domain/pets/ports/service.py
Normal file
33
standards/mcp/templates/app/domain/pets/ports/service.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""# Summary
|
||||
|
||||
CRUD service wiring for the pet aggregate via the hexa container.
|
||||
"""
|
||||
from hexa import build
|
||||
|
||||
from domain.pets.models import Pet
|
||||
from domain.pets.ports.db import PetDocument
|
||||
from infra.adapters.service import CrudsServiceAdaptor
|
||||
|
||||
|
||||
class PetCrudsService(CrudsServiceAdaptor[Pet, PetDocument]):
|
||||
"""Pet CRUD service resolving :class:`PetDocument` from a repo.
|
||||
|
||||
Notes:
|
||||
Instances are created by :func:`get_pet_service`. The ``model`` and
|
||||
``schema`` class attributes bind the domain model and its document type.
|
||||
"""
|
||||
|
||||
model = Pet
|
||||
schema = PetDocument
|
||||
|
||||
|
||||
def get_pet_service(repo) -> PetCrudsService:
|
||||
"""Build a pet CRUD service wired to ``repo``.
|
||||
|
||||
Args:
|
||||
repo (Any): Integer-id pet repository from :func:`create_pet_repo`.
|
||||
|
||||
Returns:
|
||||
PetCrudsService: Ready-to-use pet CRUD service.
|
||||
"""
|
||||
return build(PetCrudsService, instances={"repo": repo})
|
||||
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Treatments domain slice."""
|
||||
25
standards/mcp/templates/app/domain/treatments/models.py
Normal file
25
standards/mcp/templates/app/domain/treatments/models.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""# Summary
|
||||
|
||||
Treatments domain — the catalogue of treatments offered by the clinic.
|
||||
"""
|
||||
from pydantic import BaseModel
|
||||
|
||||
from domain.parents.models import Metadata
|
||||
|
||||
|
||||
class Treatment(BaseModel):
|
||||
"""A catalogue treatment.
|
||||
|
||||
Attributes:
|
||||
id (int | None): Server-assigned identifier. Defaults to None.
|
||||
label (str): Name of the treatment.
|
||||
description (str | None): Detailed description. Defaults to None.
|
||||
metadata (Metadata | None): Presentation metadata. Defaults to None.
|
||||
"""
|
||||
|
||||
id: int | None = None
|
||||
label: str
|
||||
description: str | None = None
|
||||
metadata: Metadata | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Treatments domain ports."""
|
||||
25
standards/mcp/templates/app/domain/treatments/ports/db.py
Normal file
25
standards/mcp/templates/app/domain/treatments/ports/db.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""# Summary
|
||||
|
||||
Treatments persistence ports — integer-id document and repository factory.
|
||||
"""
|
||||
from mongo_ops import BaseDocument
|
||||
from pydantic import Field
|
||||
|
||||
from domain.treatments.models import Treatment
|
||||
from infra.db.repos import create_repo
|
||||
|
||||
|
||||
class TreatmentDocument(Treatment, BaseDocument):
|
||||
"""Persisted treatment document. The integer ``id`` shadows the ObjectId."""
|
||||
|
||||
id: int | None = Field(default=None, validation_alias="_id")
|
||||
|
||||
|
||||
def create_treatment_repo():
|
||||
"""Create the treatment repository selected by ``settings.repo_backend``.
|
||||
|
||||
Returns:
|
||||
IntIdMemoryRepository | IntIdMongoRepository: Repository wired to the
|
||||
``treatments`` collection/store.
|
||||
"""
|
||||
return create_repo("treatments", TreatmentDocument)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""# Summary
|
||||
|
||||
CRUD service wiring for the treatment aggregate via the hexa container.
|
||||
"""
|
||||
from hexa import build
|
||||
|
||||
from domain.treatments.models import Treatment
|
||||
from domain.treatments.ports.db import TreatmentDocument
|
||||
from infra.adapters.service import CrudsServiceAdaptor
|
||||
|
||||
|
||||
class TreatmentCrudsService(CrudsServiceAdaptor[Treatment, TreatmentDocument]):
|
||||
"""Treatment CRUD service resolving :class:`TreatmentDocument` from a repo.
|
||||
|
||||
Notes:
|
||||
Instances are created by :func:`get_treatment_service`. The ``model``
|
||||
and ``schema`` class attributes bind the domain model and its document.
|
||||
"""
|
||||
|
||||
model = Treatment
|
||||
schema = TreatmentDocument
|
||||
|
||||
|
||||
def get_treatment_service(repo) -> TreatmentCrudsService:
|
||||
"""Build a treatment CRUD service wired to ``repo``.
|
||||
|
||||
Args:
|
||||
repo (Any): Integer-id treatment repository from
|
||||
:func:`create_treatment_repo`.
|
||||
|
||||
Returns:
|
||||
TreatmentCrudsService: Ready-to-use treatment CRUD service.
|
||||
"""
|
||||
return build(TreatmentCrudsService, instances={"repo": repo})
|
||||
3
standards/mcp/templates/app/domain/vets/__init__.py
Normal file
3
standards/mcp/templates/app/domain/vets/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Vets domain slice."""
|
||||
29
standards/mcp/templates/app/domain/vets/models.py
Normal file
29
standards/mcp/templates/app/domain/vets/models.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""# Summary
|
||||
|
||||
Vets domain — veterinarians referenced by appointments.
|
||||
"""
|
||||
from pydantic import BaseModel
|
||||
|
||||
from domain.parents.models import Metadata
|
||||
|
||||
|
||||
class Vet(BaseModel):
|
||||
"""A veterinarian.
|
||||
|
||||
Attributes:
|
||||
id (int | None): Server-assigned identifier. Defaults to None.
|
||||
name (str): Veterinarian's full name.
|
||||
specialty (str | None): Area of specialization. Defaults to None.
|
||||
email (str | None): Email address. Defaults to None.
|
||||
phone (str | None): Contact phone number. Defaults to None.
|
||||
metadata (Metadata | None): Presentation metadata. Defaults to None.
|
||||
"""
|
||||
|
||||
id: int | None = None
|
||||
name: str
|
||||
specialty: str | None = None
|
||||
email: str | None = None
|
||||
phone: str | None = None
|
||||
metadata: Metadata | None = None
|
||||
|
||||
model_config = {"extra": "ignore"}
|
||||
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Vets domain ports."""
|
||||
25
standards/mcp/templates/app/domain/vets/ports/db.py
Normal file
25
standards/mcp/templates/app/domain/vets/ports/db.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""# Summary
|
||||
|
||||
Vets persistence ports — integer-id document and repository factory.
|
||||
"""
|
||||
from mongo_ops import BaseDocument
|
||||
from pydantic import Field
|
||||
|
||||
from domain.vets.models import Vet
|
||||
from infra.db.repos import create_repo
|
||||
|
||||
|
||||
class VetDocument(Vet, BaseDocument):
|
||||
"""Persisted vet document. The integer ``id`` shadows the ObjectId."""
|
||||
|
||||
id: int | None = Field(default=None, validation_alias="_id")
|
||||
|
||||
|
||||
def create_vet_repo():
|
||||
"""Create the vet repository selected by ``settings.repo_backend``.
|
||||
|
||||
Returns:
|
||||
IntIdMemoryRepository | IntIdMongoRepository: Repository wired to the
|
||||
``vets`` collection/store.
|
||||
"""
|
||||
return create_repo("vets", VetDocument)
|
||||
33
standards/mcp/templates/app/domain/vets/ports/service.py
Normal file
33
standards/mcp/templates/app/domain/vets/ports/service.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""# Summary
|
||||
|
||||
CRUD service wiring for the vet aggregate via the hexa container.
|
||||
"""
|
||||
from hexa import build
|
||||
|
||||
from domain.vets.models import Vet
|
||||
from domain.vets.ports.db import VetDocument
|
||||
from infra.adapters.service import CrudsServiceAdaptor
|
||||
|
||||
|
||||
class VetCrudsService(CrudsServiceAdaptor[Vet, VetDocument]):
|
||||
"""Vet CRUD service resolving :class:`VetDocument` from a repo.
|
||||
|
||||
Notes:
|
||||
Instances are created by :func:`get_vet_service`. The ``model`` and
|
||||
``schema`` class attributes bind the domain model and its document type.
|
||||
"""
|
||||
|
||||
model = Vet
|
||||
schema = VetDocument
|
||||
|
||||
|
||||
def get_vet_service(repo) -> VetCrudsService:
|
||||
"""Build a vet CRUD service wired to ``repo``.
|
||||
|
||||
Args:
|
||||
repo (Any): Integer-id vet repository from :func:`create_vet_repo`.
|
||||
|
||||
Returns:
|
||||
VetCrudsService: Ready-to-use vet CRUD service.
|
||||
"""
|
||||
return build(VetCrudsService, instances={"repo": repo})
|
||||
88
standards/mcp/templates/app/infra/adapters/actions_stream.py
Normal file
88
standards/mcp/templates/app/infra/adapters/actions_stream.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""# Summary
|
||||
|
||||
Species-scoped action stream — the SSE event source behind ``stream_actions``.
|
||||
|
||||
---
|
||||
|
||||
Every pet gets a background asyncio task that emits random, species-appropriate
|
||||
behaviors (a wagging dog, a kneading cat, a chirping bird) onto per-subscriber
|
||||
queues. The HTTP handler subscribes one queue per connected client and
|
||||
unsubscribes when the response closes.
|
||||
|
||||
Notes:
|
||||
Process-local and best-effort — the stream is generated, not persisted,
|
||||
and is intentionally independent of the persistence backend.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import random
|
||||
|
||||
_BEHAVIORS_BY_SPECIES: dict[str, list[str]] = {
|
||||
"dog": ["wagging_tail", "stretching", "showing_belly", "barking", "panting"],
|
||||
"cat": ["blep", "stretching", "showing_belly", "purring", "kneading"],
|
||||
"bird": ["head_tilt", "stretching", "chirping", "wing_flap"],
|
||||
}
|
||||
|
||||
_subscribers: dict[int, list[asyncio.Queue]] = {}
|
||||
_worker_tasks: dict[int, asyncio.Task] = {}
|
||||
|
||||
|
||||
async def _behavior_worker(pet_id: int, species: str) -> None:
|
||||
"""Emit random behaviors for one pet until cancelled.
|
||||
|
||||
Args:
|
||||
pet_id (int): Pet whose subscribers receive the events.
|
||||
species (str): Animal species selecting the behavior pool.
|
||||
"""
|
||||
behaviors = _BEHAVIORS_BY_SPECIES.get(species, ["wagging_tail"])
|
||||
while True:
|
||||
action = random.choice(behaviors)
|
||||
data = json.dumps({"action": action})
|
||||
for q in _subscribers.get(pet_id, []):
|
||||
await q.put(data)
|
||||
await asyncio.sleep(random.uniform(1, 5))
|
||||
|
||||
|
||||
def _ensure_worker(pet_id: int, species: str) -> None:
|
||||
"""Start the behavior worker for ``pet_id`` if it is not running.
|
||||
|
||||
Args:
|
||||
pet_id (int): Pet identifier.
|
||||
species (str): Animal species selecting the behavior pool.
|
||||
"""
|
||||
if pet_id not in _worker_tasks or _worker_tasks[pet_id].done():
|
||||
_subscribers.setdefault(pet_id, [])
|
||||
_worker_tasks[pet_id] = asyncio.create_task(_behavior_worker(pet_id, species))
|
||||
|
||||
|
||||
async def subscribe(pet_id: int, species: str) -> asyncio.Queue:
|
||||
"""Subscribe a new listener queue for a pet's action stream.
|
||||
|
||||
Args:
|
||||
pet_id (int): Pet identifier.
|
||||
species (str): Animal species selecting the behavior pool.
|
||||
|
||||
Returns:
|
||||
asyncio.Queue: Queue that receives JSON-encoded action payloads.
|
||||
"""
|
||||
q: asyncio.Queue = asyncio.Queue()
|
||||
_subscribers.setdefault(pet_id, []).append(q)
|
||||
_ensure_worker(pet_id, species)
|
||||
return q
|
||||
|
||||
|
||||
def unsubscribe(pet_id: int, q: asyncio.Queue) -> None:
|
||||
"""Remove a listener queue and stop the worker when the last one leaves.
|
||||
|
||||
Args:
|
||||
pet_id (int): Pet identifier.
|
||||
q (asyncio.Queue): Queue returned by :func:`subscribe`.
|
||||
"""
|
||||
queues = _subscribers.get(pet_id, [])
|
||||
if q in queues:
|
||||
queues.remove(q)
|
||||
if not queues:
|
||||
task = _worker_tasks.pop(pet_id, None)
|
||||
if task and not task.done():
|
||||
task.cancel()
|
||||
_subscribers.pop(pet_id, None)
|
||||
@@ -0,0 +1,31 @@
|
||||
"""# Summary
|
||||
|
||||
Repository factory — picks the in-memory or Mongo integer-id adapter based
|
||||
on ``settings.repo_backend``.
|
||||
|
||||
---
|
||||
|
||||
Every domain slice calls :func:`create_repo` so the backend choice lives in
|
||||
one place: ``REPO_BACKEND=memory`` (default) yields the zero-dependency
|
||||
process-local store; ``REPO_BACKEND=mongo`` yields the persistent adapter.
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from config import settings
|
||||
from infra.db.repos.memory import IntIdMemoryRepository
|
||||
from infra.db.repos.mongo_int import IntIdMongoRepository
|
||||
|
||||
|
||||
def create_repo(collection: str, document_cls: type[Any]):
|
||||
"""Create the integer-id repository selected by ``settings.repo_backend``.
|
||||
|
||||
Args:
|
||||
collection (str): Collection / store name for the resource.
|
||||
document_cls (type[Any]): Document model bound to the repository.
|
||||
|
||||
Returns:
|
||||
IntIdMemoryRepository | IntIdMongoRepository: The configured repository.
|
||||
"""
|
||||
if settings.repo_backend == "mongo":
|
||||
return IntIdMongoRepository(collection, document_cls)
|
||||
return IntIdMemoryRepository(document_cls)
|
||||
|
||||
161
standards/mcp/templates/app/infra/db/repos/memory.py
Normal file
161
standards/mcp/templates/app/infra/db/repos/memory.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""# Summary
|
||||
|
||||
In-memory integer-id repository — the zero-dependency persistence adapter.
|
||||
|
||||
---
|
||||
|
||||
One process-local dictionary per resource, keyed by auto-incrementing integer
|
||||
``id``. The wire contract (``openapi.yaml``) declares integer primary keys,
|
||||
so this adapter speaks integers end-to-end and is the default backend
|
||||
(``REPO_BACKEND=memory``).
|
||||
|
||||
The method surface mirrors the mongo-ops repository duck-type consumed by
|
||||
``infra.adapters.service.CrudsServiceAdaptor``: ``create``, ``get_by_id``,
|
||||
``get_many``, ``update``, ``patch``, ``delete``.
|
||||
|
||||
Notes:
|
||||
Not thread-safe and not durable — deliberately. Swap to the Mongo
|
||||
adapter via ``REPO_BACKEND=mongo`` without touching domain code.
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IntIdMemoryRepository:
|
||||
"""Dictionary-backed repository with auto-increment integer ids.
|
||||
|
||||
Attributes:
|
||||
document_cls (type[BaseModel]): Document model instantiated for
|
||||
every stored record.
|
||||
"""
|
||||
|
||||
def __init__(self, document_cls: type[BaseModel]):
|
||||
"""Initialize an empty store for ``document_cls``.
|
||||
|
||||
Args:
|
||||
document_cls (type[BaseModel]): Document model bound to this repo.
|
||||
"""
|
||||
self.document_cls = document_cls
|
||||
self._docs: dict[int, BaseModel] = {}
|
||||
self._seq = 0
|
||||
|
||||
def _to_doc(self, data: Any) -> BaseModel:
|
||||
"""Coerce ``data`` into a document instance.
|
||||
|
||||
Args:
|
||||
data (Any): Document instance or field mapping.
|
||||
|
||||
Returns:
|
||||
BaseModel: Document built from ``data``.
|
||||
"""
|
||||
if isinstance(data, self.document_cls):
|
||||
return data
|
||||
return self.document_cls(**dict(data))
|
||||
|
||||
async def create(self, data: Any) -> BaseModel:
|
||||
"""Persist a new document with a freshly allocated integer id.
|
||||
|
||||
Args:
|
||||
data (Any): Document instance or field mapping (id ignored).
|
||||
|
||||
Returns:
|
||||
BaseModel: The stored document with ``id`` assigned.
|
||||
"""
|
||||
doc = self._to_doc(data)
|
||||
self._seq += 1
|
||||
doc.id = self._seq
|
||||
self._docs[doc.id] = doc
|
||||
return doc
|
||||
|
||||
async def get_by_id(self, id: int) -> BaseModel | None:
|
||||
"""Fetch one document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
return self._docs.get(int(id))
|
||||
|
||||
async def get_many(self, filter: dict | None = None, **_: Any) -> list[BaseModel]:
|
||||
"""List documents, optionally filtered by top-level equality.
|
||||
|
||||
Args:
|
||||
filter (dict | None): Equality filter on document fields.
|
||||
|
||||
Returns:
|
||||
list[BaseModel]: Matching documents in insertion order.
|
||||
"""
|
||||
docs = list(self._docs.values())
|
||||
if filter:
|
||||
docs = [d for d in docs if all(getattr(d, k, None) == v for k, v in filter.items())]
|
||||
return docs
|
||||
|
||||
async def update(self, id: int, data: Any) -> BaseModel | None:
|
||||
"""Replace a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
data (Any): Replacement document or field mapping.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
if int(id) not in self._docs:
|
||||
return None
|
||||
doc = self._to_doc(data)
|
||||
doc.id = int(id)
|
||||
self._docs[doc.id] = doc
|
||||
return doc
|
||||
|
||||
async def patch(self, id: int, data: dict) -> BaseModel | None:
|
||||
"""Partially update a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
data (dict): Fields to merge into the stored document.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
current = self._docs.get(int(id))
|
||||
if current is None:
|
||||
return None
|
||||
merged = current.model_dump()
|
||||
merged.update(data)
|
||||
doc = self.document_cls(**merged)
|
||||
doc.id = int(id)
|
||||
self._docs[doc.id] = doc
|
||||
return doc
|
||||
|
||||
async def delete(self, id: int) -> bool:
|
||||
"""Delete a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` when a document was removed.
|
||||
"""
|
||||
return self._docs.pop(int(id), None) is not None
|
||||
|
||||
async def delete_many(self, filter: dict | None = None) -> int:
|
||||
"""Delete every document (optionally filtered by equality).
|
||||
|
||||
Args:
|
||||
filter (dict | None): Equality filter on document fields.
|
||||
|
||||
Returns:
|
||||
int: Number of documents removed.
|
||||
"""
|
||||
victims = await self.get_many(filter)
|
||||
for doc in victims:
|
||||
self._docs.pop(doc.id, None)
|
||||
return len(victims)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove every document and reset the id counter."""
|
||||
self._docs.clear()
|
||||
self._seq = 0
|
||||
184
standards/mcp/templates/app/infra/db/repos/mongo_int.py
Normal file
184
standards/mcp/templates/app/infra/db/repos/mongo_int.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""# Summary
|
||||
|
||||
MongoDB integer-id repository — the persistent persistence adapter.
|
||||
|
||||
---
|
||||
|
||||
The wire contract declares integer primary keys, while mongo-ops'
|
||||
``BaseRepository`` allocates ``ObjectId`` keys. This adapter therefore talks
|
||||
to the shared ``MongoConnectionManager`` database directly (the Tier A
|
||||
connection lifecycle is reused; only the id strategy differs) and stores
|
||||
records with auto-incrementing integer ``_id`` values allocated from a
|
||||
``_counters`` collection.
|
||||
|
||||
The method surface mirrors the in-memory adapter exactly, so
|
||||
``CrudsServiceAdaptor`` and the domain services are backend-agnostic.
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from mongo_ops import MongoConnectionManager
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IntIdMongoRepository:
|
||||
"""Motor-backed repository with auto-increment integer ids.
|
||||
|
||||
Attributes:
|
||||
collection (str): MongoDB collection name.
|
||||
document_cls (type[BaseModel]): Document model bound to this repo.
|
||||
"""
|
||||
|
||||
def __init__(self, collection: str, document_cls: type[BaseModel]):
|
||||
"""Bind the repository to a collection and document model.
|
||||
|
||||
Args:
|
||||
collection (str): Collection name to operate on.
|
||||
document_cls (type[BaseModel]): Document model persisted here.
|
||||
"""
|
||||
self.collection = collection
|
||||
self.document_cls = document_cls
|
||||
|
||||
@property
|
||||
def _col(self):
|
||||
"""AsyncIOMotorCollection: The live collection handle."""
|
||||
return MongoConnectionManager._database[self.collection]
|
||||
|
||||
async def _next_id(self) -> int:
|
||||
"""Allocate the next integer id from the shared counters collection.
|
||||
|
||||
Returns:
|
||||
int: A monotonically increasing identifier.
|
||||
"""
|
||||
counters = MongoConnectionManager._database["_counters"]
|
||||
doc = await counters.find_one_and_update(
|
||||
{"_id": self.collection},
|
||||
{"$inc": {"seq": 1}},
|
||||
upsert=True,
|
||||
return_document=True,
|
||||
)
|
||||
return int(doc["seq"])
|
||||
|
||||
def _to_doc(self, data: Any) -> BaseModel:
|
||||
"""Coerce ``data`` into a document instance.
|
||||
|
||||
Args:
|
||||
data (Any): Document instance or field mapping.
|
||||
|
||||
Returns:
|
||||
BaseModel: Document built from ``data``.
|
||||
"""
|
||||
if isinstance(data, self.document_cls):
|
||||
return data
|
||||
return self.document_cls(**dict(data))
|
||||
|
||||
def _to_payload(self, doc: BaseModel, id: int | None = None) -> dict:
|
||||
"""Serialize a document into a Mongo-ready payload.
|
||||
|
||||
Args:
|
||||
doc (BaseModel): Document to serialize.
|
||||
id (int | None): Explicit ``_id``; taken from the doc when omitted.
|
||||
|
||||
Returns:
|
||||
dict: Mapping with an integer ``_id`` key.
|
||||
"""
|
||||
payload = doc.model_dump()
|
||||
payload.pop("id", None)
|
||||
payload["_id"] = id if id is not None else doc.id
|
||||
return payload
|
||||
|
||||
async def create(self, data: Any) -> BaseModel:
|
||||
"""Persist a new document with a freshly allocated integer id.
|
||||
|
||||
Args:
|
||||
data (Any): Document instance or field mapping (id ignored).
|
||||
|
||||
Returns:
|
||||
BaseModel: The stored document with ``id`` assigned.
|
||||
"""
|
||||
doc = self._to_doc(data)
|
||||
new_id = await self._next_id()
|
||||
await self._col.insert_one(self._to_payload(doc, new_id))
|
||||
doc.id = new_id
|
||||
return doc
|
||||
|
||||
async def get_by_id(self, id: int) -> BaseModel | None:
|
||||
"""Fetch one document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
raw = await self._col.find_one({"_id": int(id)})
|
||||
return self.document_cls.model_validate(raw) if raw else None
|
||||
|
||||
async def get_many(self, filter: dict | None = None, **_: Any) -> list[BaseModel]:
|
||||
"""List documents, optionally filtered by top-level equality.
|
||||
|
||||
Args:
|
||||
filter (dict | None): Equality filter on document fields.
|
||||
|
||||
Returns:
|
||||
list[BaseModel]: Matching documents in insertion order.
|
||||
"""
|
||||
cursor = self._col.find(filter or {})
|
||||
return [self.document_cls.model_validate(raw) async for raw in cursor]
|
||||
|
||||
async def update(self, id: int, data: Any) -> BaseModel | None:
|
||||
"""Replace a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
data (Any): Replacement document or field mapping.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
doc = self._to_doc(data)
|
||||
payload = self._to_payload(doc, int(id))
|
||||
result = await self._col.replace_one({"_id": int(id)}, payload)
|
||||
if result.matched_count == 0:
|
||||
return None
|
||||
doc.id = int(id)
|
||||
return doc
|
||||
|
||||
async def patch(self, id: int, data: dict) -> BaseModel | None:
|
||||
"""Partially update a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
data (dict): Fields to merge into the stored document.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
updates = {k: v for k, v in data.items() if k != "id"}
|
||||
result = await self._col.update_one({"_id": int(id)}, {"$set": updates})
|
||||
if result.matched_count == 0:
|
||||
return None
|
||||
return await self.get_by_id(id)
|
||||
|
||||
async def delete(self, id: int) -> bool:
|
||||
"""Delete a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` when a document was removed.
|
||||
"""
|
||||
result = await self._col.delete_one({"_id": int(id)})
|
||||
return result.deleted_count > 0
|
||||
|
||||
async def delete_many(self, filter: dict | None = None) -> int:
|
||||
"""Delete every document (optionally filtered by equality).
|
||||
|
||||
Args:
|
||||
filter (dict | None): Equality filter on document fields.
|
||||
|
||||
Returns:
|
||||
int: Number of documents removed.
|
||||
"""
|
||||
result = await self._col.delete_many(filter or {})
|
||||
return result.deleted_count
|
||||
@@ -1,16 +1,31 @@
|
||||
"""# Summary
|
||||
|
||||
Infrastructure initialization — connects MongoDB and wires all repos and services.
|
||||
Shared bootstrap for both the FastAPI app and the worker, guarded against
|
||||
repeated initialization via a module-level flag.
|
||||
Composition root — connects MongoDB (when selected), wires all repos and
|
||||
services, and returns the resources dict. Shared bootstrap for the FastAPI
|
||||
app and the seed/drop scripts, guarded against repeated initialization via a
|
||||
module-level flag.
|
||||
|
||||
---
|
||||
|
||||
The ``memory`` backend skips the Mongo connection entirely — repos are
|
||||
process-local dictionaries, so a plain ``uvicorn main:app`` runs with zero
|
||||
external services.
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from config import settings
|
||||
from domain.item.ports.db import create_item_repo
|
||||
from domain.item.ports.service import get_item_service
|
||||
from domain.job.ports.db import create_job_repo
|
||||
from domain.job.ports.service import get_job_service
|
||||
from domain.appointments.ports.db import create_appointment_repo
|
||||
from domain.appointments.ports.service import get_appointment_service
|
||||
from domain.jobs.ports.db import create_job_repo
|
||||
from domain.jobs.ports.service import get_job_service
|
||||
from domain.parents.ports.db import create_parent_repo
|
||||
from domain.parents.ports.service import get_parent_service
|
||||
from domain.pets.ports.db import create_pet_repo
|
||||
from domain.pets.ports.service import get_pet_service
|
||||
from domain.treatments.ports.db import create_treatment_repo
|
||||
from domain.treatments.ports.service import get_treatment_service
|
||||
from domain.vets.ports.db import create_vet_repo
|
||||
from domain.vets.ports.service import get_vet_service
|
||||
from infra.db.setup import setup_database, shutdown_database
|
||||
|
||||
_resources: dict[str, Any] = {}
|
||||
@@ -18,25 +33,40 @@ _initialized: bool = False
|
||||
|
||||
|
||||
async def init() -> dict[str, Any]:
|
||||
"""Connect MongoDB, wire all repos and services, and return the resources dict.
|
||||
"""Wire all repos and services, and return the resources dict.
|
||||
|
||||
Idempotent: repeated calls short-circuit and return the cached resources.
|
||||
Only the ``mongo`` backend performs a network connection here.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Named services keyed by resource name.
|
||||
Dict[str, Any]: Named services keyed by resource name
|
||||
(``<resource>_svc`` and ``<resource>_repo``).
|
||||
"""
|
||||
global _resources, _initialized
|
||||
if _initialized:
|
||||
return _resources
|
||||
|
||||
await setup_database(uri=settings.mongo_uri, db_name=settings.mongo_db)
|
||||
if settings.repo_backend == "mongo":
|
||||
await setup_database(uri=settings.mongo_uri, db_name=settings.mongo_db)
|
||||
|
||||
item_repo = create_item_repo()
|
||||
parent_repo = create_parent_repo()
|
||||
vet_repo = create_vet_repo()
|
||||
treatment_repo = create_treatment_repo()
|
||||
pet_repo = create_pet_repo()
|
||||
appointment_repo = create_appointment_repo()
|
||||
job_repo = create_job_repo()
|
||||
|
||||
_resources["item_repo"] = item_repo
|
||||
_resources["parent_repo"] = parent_repo
|
||||
_resources["vet_repo"] = vet_repo
|
||||
_resources["treatment_repo"] = treatment_repo
|
||||
_resources["pet_repo"] = pet_repo
|
||||
_resources["appointment_repo"] = appointment_repo
|
||||
_resources["job_repo"] = job_repo
|
||||
_resources["item_svc"] = get_item_service(item_repo)
|
||||
_resources["parent_svc"] = get_parent_service(parent_repo)
|
||||
_resources["vet_svc"] = get_vet_service(vet_repo)
|
||||
_resources["treatment_svc"] = get_treatment_service(treatment_repo)
|
||||
_resources["pet_svc"] = get_pet_service(pet_repo)
|
||||
_resources["appointment_svc"] = get_appointment_service(appointment_repo)
|
||||
_resources["job_svc"] = get_job_service(job_repo)
|
||||
|
||||
_initialized = True
|
||||
@@ -44,8 +74,9 @@ async def init() -> dict[str, Any]:
|
||||
|
||||
|
||||
async def shutdown() -> None:
|
||||
"""Disconnect MongoDB and reset the resources cache."""
|
||||
"""Disconnect MongoDB (if connected) and reset the resources cache."""
|
||||
global _initialized
|
||||
await shutdown_database()
|
||||
if settings.repo_backend == "mongo":
|
||||
await shutdown_database()
|
||||
_resources.clear()
|
||||
_initialized = False
|
||||
_initialized = False
|
||||
|
||||
146
standards/mcp/templates/app/interfaces/_common.py
Normal file
146
standards/mcp/templates/app/interfaces/_common.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""# Summary
|
||||
|
||||
Shared interface helpers — service resolution, serialization, pagination and
|
||||
foreign-key expansion used by every route module.
|
||||
|
||||
---
|
||||
|
||||
FK fields on the wire may arrive either as a bare integer id (from an
|
||||
``x-fk`` picker) or as an already-expanded object. The wire contract always
|
||||
*returns* fully expanded objects, so create/update handlers normalize the
|
||||
input through :func:`expand_ref` before persisting.
|
||||
"""
|
||||
from fastapi import HTTPException, Request
|
||||
|
||||
|
||||
def svc(request: Request, name: str):
|
||||
"""Resolve a CRUD service from app state by resource name.
|
||||
|
||||
Args:
|
||||
request (Request): Incoming request whose app.state carries resources.
|
||||
name (str): Resource key suffix, e.g. ``"parent"`` → ``parent_svc``.
|
||||
|
||||
Returns:
|
||||
Any: The named CRUD service.
|
||||
"""
|
||||
return request.app.state.db_resources[f"{name}_svc"]
|
||||
|
||||
|
||||
def dump(doc) -> dict:
|
||||
"""Serialize a document to a JSON-ready mapping with an integer id.
|
||||
|
||||
Args:
|
||||
doc (Any): Pydantic document to serialize.
|
||||
|
||||
Returns:
|
||||
dict: JSON-mode mapping (``by_alias`` so ``_id`` maps back to ``id``).
|
||||
"""
|
||||
return doc.model_dump(mode="json", by_alias=True)
|
||||
|
||||
|
||||
def paginate(items: list, limit: int, offset: int) -> dict:
|
||||
"""Slice ``items`` into the ``{total, items}`` pagination envelope.
|
||||
|
||||
A ``limit`` of 0 means "all remaining items" (used by react-openapi for
|
||||
FK option lists and counts).
|
||||
|
||||
Args:
|
||||
items (list): Full result list.
|
||||
limit (int): Page size; ``0`` returns everything after ``offset``.
|
||||
offset (int): Number of items to skip.
|
||||
|
||||
Returns:
|
||||
dict: ``{"total": int, "items": list}``.
|
||||
"""
|
||||
offset = max(offset, 0)
|
||||
page = items[offset:] if limit == 0 else items[offset : offset + limit]
|
||||
return {"total": len(items), "items": page}
|
||||
|
||||
|
||||
async def expand_ref(svc_obj, value, label: str) -> dict | None:
|
||||
"""Normalize a foreign-key reference into a fully expanded object.
|
||||
|
||||
Args:
|
||||
svc_obj (Any): Target resource CRUD service used for lookups.
|
||||
value (dict | int | None): Bare id, ``{"id": N}`` stub, or full object.
|
||||
label (str): Human label used in 422 error messages.
|
||||
|
||||
Returns:
|
||||
dict | None: Expanded object, or ``None`` when ``value`` is ``None``.
|
||||
|
||||
Raises:
|
||||
HTTPException: 422 when the reference cannot be resolved to a document.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, dict):
|
||||
if set(value.keys()) == {"id"}:
|
||||
value = value["id"]
|
||||
else:
|
||||
return value
|
||||
if isinstance(value, int):
|
||||
doc = await svc_obj.read(value)
|
||||
if doc is None:
|
||||
raise HTTPException(status_code=422, detail=f"{label} {value} not found")
|
||||
return dump(doc)
|
||||
raise HTTPException(status_code=422, detail=f"invalid {label} reference")
|
||||
|
||||
|
||||
async def expand_ref_list(svc_obj, value, label: str) -> list[dict]:
|
||||
"""Normalize a list foreign-key reference into expanded objects.
|
||||
|
||||
Args:
|
||||
svc_obj (Any): Target resource CRUD service used for lookups.
|
||||
value (list | None): List of bare ids, stubs, or full objects.
|
||||
label (str): Human label used in 422 error messages.
|
||||
|
||||
Returns:
|
||||
list[dict]: Expanded objects.
|
||||
|
||||
Raises:
|
||||
HTTPException: 422 when any reference cannot be resolved.
|
||||
"""
|
||||
if value is None:
|
||||
return []
|
||||
if not isinstance(value, list):
|
||||
raise HTTPException(status_code=422, detail=f"invalid {label} list")
|
||||
results = []
|
||||
for entry in value:
|
||||
results.append(await expand_ref(svc_obj, entry, label))
|
||||
return results
|
||||
|
||||
|
||||
async def read_or_404(svc_obj, id: int, label: str):
|
||||
"""Read a document by id or raise a 404 HTTPException.
|
||||
|
||||
Args:
|
||||
svc_obj (Any): CRUD service to read from.
|
||||
id (int): Document identifier.
|
||||
label (str): Human label used in the 404 detail message.
|
||||
|
||||
Returns:
|
||||
Any: The found document.
|
||||
|
||||
Raises:
|
||||
HTTPException: 404 when no document matches ``id``.
|
||||
"""
|
||||
doc = await svc_obj.read(id)
|
||||
if doc is None:
|
||||
raise HTTPException(status_code=404, detail=f"{label} not found")
|
||||
return doc
|
||||
|
||||
|
||||
async def delete_or_404(svc_obj, id: int, label: str) -> None:
|
||||
"""Delete a document by id or raise a 404 HTTPException.
|
||||
|
||||
Args:
|
||||
svc_obj (Any): CRUD service to delete from.
|
||||
id (int): Document identifier.
|
||||
label (str): Human label used in the 404 detail message.
|
||||
|
||||
Raises:
|
||||
HTTPException: 404 when no document matches ``id``.
|
||||
"""
|
||||
ok = await svc_obj.delete(id)
|
||||
if not ok:
|
||||
raise HTTPException(status_code=404, detail=f"{label} not found")
|
||||
@@ -0,0 +1,3 @@
|
||||
"""# Summary
|
||||
|
||||
Appointments interfaces."""
|
||||
135
standards/mcp/templates/app/interfaces/appointments/routes.py
Normal file
135
standards/mcp/templates/app/interfaces/appointments/routes.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""# Summary
|
||||
|
||||
Appointments route handlers — names must match openapi.yaml operationIds.
|
||||
|
||||
Notes:
|
||||
``pet``, ``vet`` and ``treatment`` are foreign keys expanded to full
|
||||
objects on read and normalized from ids on write. The list endpoint
|
||||
supports date-prefix and pet/vet id filters.
|
||||
"""
|
||||
from fastapi import Request, Response
|
||||
|
||||
from interfaces._common import (
|
||||
delete_or_404,
|
||||
dump,
|
||||
expand_ref,
|
||||
paginate,
|
||||
read_or_404,
|
||||
svc,
|
||||
)
|
||||
|
||||
|
||||
async def list_appointments(
|
||||
request: Request,
|
||||
limit: int = 20,
|
||||
offset: int = 0,
|
||||
date: str | None = None,
|
||||
vet: int | None = None,
|
||||
pet: int | None = None,
|
||||
) -> dict:
|
||||
"""List appointments (paginated, filterable by date/vet/pet).
|
||||
|
||||
Args:
|
||||
request (Request): Incoming request.
|
||||
limit (int): Page size. Defaults to 20.
|
||||
offset (int): Items to skip. Defaults to 0.
|
||||
date (str | None): ISO date prefix to match (e.g. ``2025-06-01``).
|
||||
vet (int | None): Filter by embedded vet id.
|
||||
pet (int | None): Filter by embedded pet id.
|
||||
|
||||
Returns:
|
||||
dict: ``{"total": int, "items": list[Appointment]}``.
|
||||
"""
|
||||
docs = await svc(request, "appointment").search({})
|
||||
items = []
|
||||
for d in docs:
|
||||
row = dump(d)
|
||||
if date and not str(row.get("date", "")).startswith(date):
|
||||
continue
|
||||
if vet is not None and (row.get("vet") or {}).get("id") != vet:
|
||||
continue
|
||||
if pet is not None and (row.get("pet") or {}).get("id") != pet:
|
||||
continue
|
||||
items.append(row)
|
||||
return paginate(items, limit, offset)
|
||||
|
||||
|
||||
async def create_appointment(request: Request, body: dict, response: Response) -> dict:
|
||||
"""Create an appointment; FK fields may be bare ids or expanded objects.
|
||||
|
||||
Args:
|
||||
request (Request): Incoming request.
|
||||
body (dict): Appointment fields (``id`` ignored; FKs normalized).
|
||||
response (Response): Response object; set to 201 on success.
|
||||
|
||||
Returns:
|
||||
dict: The created appointment with FKs expanded.
|
||||
|
||||
Raises:
|
||||
HTTPException: 422 when an FK reference cannot be resolved.
|
||||
"""
|
||||
response.status_code = 201
|
||||
body.pop("id", None)
|
||||
body["pet"] = await expand_ref(svc(request, "pet"), body.get("pet"), "pet")
|
||||
body["vet"] = await expand_ref(svc(request, "vet"), body.get("vet"), "vet")
|
||||
body["treatment"] = await expand_ref(
|
||||
svc(request, "treatment"), body.get("treatment"), "treatment"
|
||||
)
|
||||
doc = await svc(request, "appointment").create(body)
|
||||
return dump(doc)
|
||||
|
||||
|
||||
async def get_appointment(request: Request, id: int) -> dict:
|
||||
"""Fetch one appointment by id.
|
||||
|
||||
Args:
|
||||
request (Request): Incoming request.
|
||||
id (int): Appointment identifier.
|
||||
|
||||
Returns:
|
||||
dict: The appointment document.
|
||||
|
||||
Raises:
|
||||
HTTPException: 404 when the appointment does not exist.
|
||||
"""
|
||||
return dump(await read_or_404(svc(request, "appointment"), id, "appointment"))
|
||||
|
||||
|
||||
async def update_appointment(request: Request, id: int, body: dict) -> dict:
|
||||
"""Replace an appointment by id; FK fields may be ids or objects.
|
||||
|
||||
Args:
|
||||
request (Request): Incoming request.
|
||||
id (int): Appointment identifier.
|
||||
body (dict): Full replacement payload.
|
||||
|
||||
Returns:
|
||||
dict: The updated appointment with FKs expanded.
|
||||
|
||||
Raises:
|
||||
HTTPException: 404 when the appointment does not exist; 422 on bad FKs.
|
||||
"""
|
||||
await read_or_404(svc(request, "appointment"), id, "appointment")
|
||||
body.pop("id", None)
|
||||
body["pet"] = await expand_ref(svc(request, "pet"), body.get("pet"), "pet")
|
||||
body["vet"] = await expand_ref(svc(request, "vet"), body.get("vet"), "vet")
|
||||
body["treatment"] = await expand_ref(
|
||||
svc(request, "treatment"), body.get("treatment"), "treatment"
|
||||
)
|
||||
doc = await svc(request, "appointment").update(id, body)
|
||||
return dump(doc)
|
||||
|
||||
|
||||
async def delete_appointment(request: Request, id: int, response: Response) -> None:
|
||||
"""Delete an appointment by id.
|
||||
|
||||
Args:
|
||||
request (Request): Incoming request.
|
||||
id (int): Appointment identifier.
|
||||
response (Response): Response object; set to 204 on success.
|
||||
|
||||
Raises:
|
||||
HTTPException: 404 when the appointment does not exist.
|
||||
"""
|
||||
await delete_or_404(svc(request, "appointment"), id, "appointment")
|
||||
response.status_code = 204
|
||||
@@ -1,42 +0,0 @@
|
||||
"""# Summary
|
||||
|
||||
Item route handlers — names must match openapi.yaml operationIds.
|
||||
"""
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from domain.item.models import Item
|
||||
|
||||
|
||||
def svc(request: Request):
|
||||
"""Resolve the item CRUD service from app state."""
|
||||
return request.app.state.db_resources["item_svc"]
|
||||
|
||||
|
||||
async def create_item(request: Request, body: dict):
|
||||
"""Create an item from the request body."""
|
||||
data = body.model_dump() if hasattr(body, "model_dump") else dict(body)
|
||||
doc = await svc(request).create(Item(**data))
|
||||
return JSONResponse(status_code=201, content=doc.model_dump(mode="json", by_alias=True))
|
||||
|
||||
|
||||
async def get_item(request: Request, id: str):
|
||||
"""Fetch one item by id."""
|
||||
doc = await svc(request).read(id)
|
||||
if doc is None:
|
||||
return JSONResponse(status_code=404, content={"detail": "item not found"})
|
||||
return doc.model_dump(mode="json", by_alias=True)
|
||||
|
||||
|
||||
async def list_items(request: Request):
|
||||
"""List all items."""
|
||||
docs = await svc(request).search({})
|
||||
return [d.model_dump(mode="json", by_alias=True) for d in docs]
|
||||
|
||||
|
||||
async def delete_item(request: Request, id: str):
|
||||
"""Delete one item by id."""
|
||||
ok = await svc(request).delete(id)
|
||||
if not ok:
|
||||
return JSONResponse(status_code=404, content={"detail": "item not found"})
|
||||
return {"deleted": True}
|
||||
@@ -1,34 +0,0 @@
|
||||
"""# Summary
|
||||
|
||||
Job route handlers — names must match openapi.yaml operationIds.
|
||||
"""
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from domain.job.models import Job
|
||||
|
||||
|
||||
def svc(request: Request):
|
||||
"""Resolve the job CRUD service from app state."""
|
||||
return request.app.state.db_resources["job_svc"]
|
||||
|
||||
|
||||
async def create_job(request: Request, body: dict):
|
||||
"""Create a job from the request body."""
|
||||
data = body.model_dump() if hasattr(body, "model_dump") else dict(body)
|
||||
doc = await svc(request).create(Job(**data))
|
||||
return JSONResponse(status_code=201, content=doc.model_dump(mode="json", by_alias=True))
|
||||
|
||||
|
||||
async def get_job(request: Request, id: str):
|
||||
"""Fetch one job by id."""
|
||||
doc = await svc(request).read(id)
|
||||
if doc is None:
|
||||
return JSONResponse(status_code=404, content={"detail": "job not found"})
|
||||
return doc.model_dump(mode="json", by_alias=True)
|
||||
|
||||
|
||||
async def list_jobs(request: Request):
|
||||
"""List all jobs."""
|
||||
docs = await svc(request).search({})
|
||||
return [d.model_dump(mode="json", by_alias=True) for d in docs]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user