Skip to content

v0.12.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 01:14
· 301 commits to main since this release
713d54a

Highlights

The models are the schema. 90 hand-written table declarations are gone. aegis init and aegis add run the generated project's own migration generator in its venv: replay the existing revisions onto a scratch database, autogenerate the difference against the model metadata, prune to additive operations, write one revision per service. Ownership comes from where a model lives, cyclic foreign keys emit once both tables exist, and a generated revision declares its own stamp signature so startup re-adoption reads it off the revision rather than a parallel table. A Postgres oracle asserts the diff against the models is empty, and every generated project carries the same check on SQLite. migration_generator.py drops from 5,080 lines to 775.

An update stops spending your work quietly. Answers are derived from the project rather than guessed from template defaults, so a question added after your project was generated no longer renders with an answer nobody chose. Every conflict is reported, inline <<<<<<< markers as well as .rej files, and aegis update --finish completes the job once you have resolved them: missing migrations, post-generation tasks, baseline advance, backup tag cleanup. Before you deploy, the update names the .env keys the new Settings no longer declares and the behavior changes this version range crosses, both of which are knowable at update time and invisible afterwards.

An N+1 gate on every database stack. Generated database projects get queryspy in the dev extra and a make check-queries target, inert on a plain pytest run. The framework's stack matrix runs its pytest step with --queryspy-strict against one baseline covering all 14 database stacks: a new finding fails, the known ones do not. make queryspy-baseline regenerates the fixture when a model change rewrites statement text.

Smaller things that bite less often but bite hard. /health/ is a constant-time liveness probe with component status behind a cached /health/detailed, dropping idle CPU on a full stack from 13.4% to 0.26%. aegis deploy-exec runs one command against a deployment without hand-writing the ssh and compose-file chain. A provider is described once in a registry instead of nine times across four files, and OpenRouter is one of them.

Changed

  • Revisions are derived from the models at init and add; a documents-only stack ships a migration at last, because the gate is now the function that decides which services need one.
  • aegis update derives ollama_mode from the URL the project talks to and the finance provider flags from the settings it renders, and computes the at-risk answer set from copier.yml history instead of a hand-kept list.
  • A conflicted update records its target and can be completed with aegis update --finish.
  • uv sync --upgrade runs on the update path, orphan scheduler jobs are exported before the sweep deletes them, and AEGIS_STACK_TAG defaults to the project slug instead of a shared aegis-stack:latest.
  • The scheduler proves liveness with a heartbeat file rather than an HTTP probe; system/health.py is split by component, 1456 lines to 714.
  • A scaffolded plugin pins the aegis-stack that made it, and a plugin does not have to be on PyPI to be discovered.

Removed

  • The finance_import question, which was asked, stored, and never read by anything. The importers ship on the finance service alone, so deleting it changes no output.

Fixed

  • A generated revision survives SQLite, where adding an index or a constraint rebuilds the table. create_index(if_not_exists=True) is fatal in that path, and the rebuild refuses to proceed around an unnamed constraint, so a 0.10.1 project with auth and ai could not update at all.
  • The conflict report no longer tells you to "keep the right side", which is the template render and deletes your own change in every case the advice is printed for. Markers are labelled your project and new template.
  • LOGFIRE_TOKEN in .env reaches logfire.configure(). The guard read it through pydantic-settings, logfire read os.environ, and the app stopped importing outside a container.
  • --to-version HEAD stamps a parseable version, so the compatibility gate is not silently disabled for the rest of the project's life, and a run that used --template-path says the project is now pinned to a local path.
  • A pending resume no longer swallows --to-version, --template-path or --dry-run; a dry run with pending state used to run post-gen and delete the backup tag.
  • Updates deliver migrations the project predates, and a failed alembic upgrade is no longer reported as success.
  • An update no longer plants an empty file for every gated-off template that changed.
  • A merged pyproject.toml that no longer parses is a conflict, instead of silently breaking every Python merge that follows it.
  • Startup re-adoption actually stamps: the inspector was used after its session closed and every failure was swallowed.
  • Every async session runs with foreign keys on, and SQLite transactions open BEGIN IMMEDIATE so a second writer queues instead of failing.
  • STORAGE_ROOT reaches every container; four services replaced the anchor's environment list wholesale.
  • A Postgres project's test suite runs.

Upgrading

uvx aegis-stack@0.12.0 update

Installation

pip install aegis-stack==0.12.0

Or run directly:

uvx aegis-stack init my-project

Links

What's Changed

  • Plugin scaffold pins its aegis-stack; document the source-only path by @lbedner in #1092
  • CLAUDE.md: name the one async CLI exemplar by @lbedner in #1093
  • Adopt queryspy: N+1 gate on the stack matrix, three query fixes by @lbedner in #1095
  • Three bugs from a real ledger, and a payee named once stays named by @lbedner in #1094
  • Refresh the query baseline after #1094; make regeneration one command by @lbedner in #1097
  • STORAGE_ROOT reaches every container by @lbedner in #1086
  • Add deploy-exec: run a one-off command on a deployment, scriptably by @lbedner in #1098
  • Generate the documents migration at init; one source of truth for the gate by @lbedner in #1100
  • A provider is described once, and OpenRouter is one of them by @lbedner in #1099
  • Add a small, injectable GraphQL client to the template by @lbedner in #1102
  • A bill's amount describes rows that were never its payments by @lbedner in #1103
  • Health checks landed; Postgres-project tests run; slow suites run nightly by @lbedner in #1105
  • update: gated-off stubs, predated migrations, and honest failure reporting by @lbedner in #1107
  • Models as the source of schema truth: oracles, and the drift ported by @lbedner in #1108
  • Update Path Hardening: operator notices + conflict handling (#1020, #1029, #1016, #1018) by @lbedner in #1111
  • Derive alembic revisions from the models at init and add by @lbedner in #1112
  • Update Path Hardening: re-lock, orphan export, image tag, sentinel owner (#1019, #1026, #1027, #1110) by @lbedner in #1113
  • Four fixes found downstream, each generic by @lbedner in #1114
  • update: merge ruff config last, flag a broken pyproject as a conflict by @lbedner in #1115
  • Revisions sign themselves; the table declarations are gone by @lbedner in #1116
  • update: close the six hardening gaps found updating aegis-pulse by @lbedner in #1124
  • update: derive the answers it would otherwise invent, and keep it that way by @lbedner in #1126
  • update: compute which answers are at risk instead of listing them by @lbedner in #1127
  • Delete finance_import: a question nothing ever read by @lbedner in #1128
  • Startup recovery and deploy sync: stop losing what was removed by @lbedner in #1129
  • update: a pending resume no longer swallows the flags you just typed by @lbedner in #1133
  • update: stop telling users to delete their own code, and stamp a real version by @lbedner in #1137
  • Release 0.12.0rc1 by @lbedner in #1138
  • kit: a strip that can count, a dialog title, and money with one home by @lbedner in #1139
  • update: a stale default is not a removed key, and must not read like one by @lbedner in #1140
  • Upgrade coverage is a matrix: every stack shape, from every release by @lbedner in #1141
  • Release 0.12.0rc2 by @lbedner in #1142
  • A generated revision has to survive SQLite, where a table is rebuilt by @lbedner in #1143
  • Release 0.12.0rc3 by @lbedner in #1144
  • Release 0.12.0 by @lbedner in #1145

Full Changelog: v0.11.1...v0.12.0