Initial public ModelForge release

This commit is contained in:
Jens
2026-09-01 21:30:16 +02:00
commit 7082ab955a
490 changed files with 104252 additions and 0 deletions
@@ -0,0 +1,24 @@
# ADR-0001: Capability-first routing
**Status:** Accepted
## Context
Directly binding projects to model IDs creates pervasive coupling. Model upgrades then require application code/config changes and encourage accidental use of incompatible model revisions.
## Decision
Projects bind to versioned logical capabilities. The ModelForge gateway resolves the capability, project identity, channel and policy to a concrete deployment.
## Consequences
Positive:
- model upgrades become infrastructure lifecycle operations;
- project code remains stable;
- benchmark and migration policy can attach to capabilities;
- fallbacks can be expressed centrally.
Tradeoff:
- ModelForge must define precise capability contracts rather than a generic pass-through API only.
@@ -0,0 +1,11 @@
# ADR-0002: Separate model, artifact and deployment concepts
**Status:** Accepted
## Decision
Represent upstream logical models, immutable revisions, concrete/downloaded artifacts, derived artifacts, runtime profiles and deployments as distinct entities.
## Rationale
Performance and compatibility depend on much more than a model name. Quantization, runtime, context settings, launch parameters and exact revision can materially change memory use, speed and output.
@@ -0,0 +1,15 @@
# ADR-0003: Single-GPU first, multi-node ready
**Status:** Accepted
## Decision
Build the initial scheduler for one local NVIDIA GPU using Docker Compose and NVML, while keeping `ComputeNode` and `Accelerator` abstractions in the domain model.
## Rejected initially
- Kubernetes
- distributed inference scheduling
- multi-tenant cloud architecture
These add operational cost without solving the first product problem.
@@ -0,0 +1,18 @@
# ADR-0004: Embedding-space changes require explicit migration
**Status:** Accepted
## Decision
A deployment change for a vector-producing capability is `requires_reindex` unless compatibility has been explicitly proven by the capability contract.
## Required migration shape
1. create a shadow index/version;
2. backfill using the candidate embedding deployment;
3. validate corpus and counts;
4. execute project-specific retrieval benchmarks/shadow queries;
5. promote the new index atomically;
6. retain old index and deployment for rollback until retention expires.
Matching vector dimension alone is never evidence of semantic compatibility.
@@ -0,0 +1,26 @@
# ADR-0005: Validated manifests and relational M0 foundation
**Status:** Accepted
## Context
The starter duplicated candidate metadata in Python and YAML and had no persistent schema. Design
invariants therefore could not be checked before M1/M2 work.
## Decision
Version-controlled YAML is the read-only M0 seed source and is parsed through strict Pydantic schemas.
PostgreSQL is the future operational source of truth using distinct SQLAlchemy entities and an Alembic
baseline. API routes consume a registry service rather than raw YAML or module constants. Audit events
are append-only and hash-chain ready. Canonical endpoints use `/api/v1`.
GPU allocation is an optional Compose overlay; the base development stack is CPU-compatible. Redis is
profiled until a queue-owning milestone needs it.
## Consequences
- Invalid capability/project/security manifests fail readiness and tests.
- The starter no longer presents seed metadata as a deployment.
- M1 can persist hardware without reshaping model lifecycle entities.
- Later schema changes require explicit Alembic operations; the metadata-driven creation is restricted
to the reviewed initial baseline revision.
@@ -0,0 +1,23 @@
# ADR-0006: Stable hardware identity and typed observations
**Status:** Accepted
## Context
Hostnames, GPU indexes and current telemetry are not stable identities. Containers can also expose an
image-local machine ID that is identical on different hosts. Missing metrics cannot safely be stored
as zero or ambiguous nulls.
## Decision
Direct nodes derive a privacy-preserving UUIDv5 from the OS machine ID; container deployments use a
persisted UUID volume or explicit configured identity. GPU UUID is the accelerator identity within a
node. Optional observations pair nullable values with `known`, `unknown`, `unsupported`, `unavailable`
or `temporarily_failed`. Inventory and latest telemetry persist separately. Reconciliation preserves
temporarily absent hardware.
## Consequences
Repeated scans are idempotent, container restarts preserve node identity, and absence is not confused
with a measured zero. Moving the same GPU between nodes is represented honestly as a different node-
accelerator relation. M1 stores only latest telemetry; history/export belongs to observability work.
@@ -0,0 +1,29 @@
# ADR-0007: Outbound authenticated compute-node agent
- Status: accepted
- Date: 2026-08-25
## Context
The central control plane must manage hardware on separate GPU servers without conflating the API
host with the compute node. SSH orchestration, Docker socket exposure and inbound node services widen
the trust boundary and do not provide a stable reporting protocol.
## Decision
Run a lightweight unprivileged agent on each compute node. It initiates HTTPS requests to a
versioned central API, reports typed inventory/telemetry and heartbeats, and persists its identity,
credential and stream sequences. Enrollment secrets are random, short-lived, single-use and stored
only as SHA-256 hashes. Issued credentials are random, node-scoped, hash-only at rest, revocable and
never returned by list APIs. Administrative onboarding uses a separately configured operator secret.
The control plane uses server-received heartbeat time for liveness and retains agent observation time
for provenance. Node roles, labels and eligibility are operator-managed metadata; inventory never
grants production eligibility automatically.
## Consequences
The compute server needs only outbound API reachability. Lost agent state requires explicit
revocation/re-enrollment. HTTPS termination and CA trust are deployment responsibilities. Protocol
evolution must remain explicit and test older/replayed/incompatible reports. Runtime execution is
intentionally absent until M5/M6 define adapters and scheduling.
@@ -0,0 +1,31 @@
# ADR-0008: Separate content identity from node-scoped storage location
## Status
Accepted — 2026-08-25
## Context
The M0 schema stored one `storage_uri` directly on an artifact and one source directly on a derived
artifact. Operational registry use requires replicas, missing locations, remote-only metadata and
transformations with multiple inputs. Treating a path as identity would make moves look like new
artifacts and would weaken provenance.
## Decision
Artifact content identity is the exact revision plus SHA-256. `ArtifactLocation` associates content
with a node-scoped `StorageRoot` and relative path. A derived output has ordered N-source lineage with
captured source hashes. Storage capacity is observed and placement fails closed when unknown or when
reserve policy would be crossed.
Legacy `storage_uri` and primary-source columns remain nullable migration compatibility fields in M2;
new services never use them as identity.
## Consequences
- replicas and moves do not change content identity;
- missing/corrupt state is location-aware and auditable;
- delete checks can name exact provenance/location dependencies;
- remote compute storage can be configured without exposing workers publicly;
- later download/runtime milestones can consume this boundary without redesigning the registry;
- the additional joins are deliberate and preferable to provenance ambiguity.
@@ -0,0 +1,17 @@
# ADR-0009: Node-local artifact acquisition
## Status
Accepted — 2026-08-25
## Decision
The control plane owns Hub metadata, immutable plans and job state. The selected compute node's
outbound agent downloads and verifies bytes against its locally mounted approved storage root.
Artifact operations are a closed typed protocol, not shell/SSH/command execution.
## Consequences
Capacity and path safety are evaluated where the bytes live, GPU Node needs no inbound service, and
credentials/jobs remain node-scoped. SSH remains deployment/debug tooling outside the ModelForge
runtime protocol.
@@ -0,0 +1,17 @@
# ADR-0010: Immutable, explicitly approved download plans
## Status
Accepted — 2026-08-25
## Decision
A download is derived from an immutable exact commit and selected file inventory. Its canonical
payload, target, expected bytes/checksums, preflights, expiry and idempotency hash cannot change.
Plan creation and approval are separate actions. Mutable refs and stale/unapproved plans cannot
execute.
## Consequences
Operators review the precise transfer before bytes move; retries reuse the same intent; upstream
movement requires a new revision and plan; duplicate execution resolves to one job.
@@ -0,0 +1,18 @@
# ADR-0011: Set-level quarantine and atomic promotion
## Status
Accepted — 2026-08-25
## Decision
All required files for an artifact set are downloaded to a job-scoped directory inside the target
filesystem. Static verification precedes one atomic directory rename to the exact-revision store.
The database registers artifacts only after that promotion report and records static integrity as
distinct from later security/runtime approval.
## Consequences
Partial sets cannot appear locally verified, cross-filesystem partial moves are avoided, retries
can reuse durable partials, and a sidecar manifest supports reconciliation if the file move and DB
transaction are interrupted.
@@ -0,0 +1,15 @@
# ADR-0012: Typed static runtime worker boundary
Status: accepted (M4)
## Decision
Run heavy ML dependencies in a separately built, static Compose worker. It polls outbound with a
node credential, accepts only the `runtime.probe.v1` schema, mounts exact artifacts read-only and
uses a child process per probe. Do not expose Docker, shell, SSH, generic Python or public runtime
control surfaces.
## Consequences
Runtime images are larger but independently pinned. Node Agent remains lightweight. Adding an
adapter requires a typed implementation and image revision; remote payloads cannot invent launches.
@@ -0,0 +1,14 @@
# ADR-0013: Exact-ArtifactSet LAB execution approval
Status: accepted (M4)
## Decision
Separate integrity verification from permission to execute. Store LAB approval against one exact
ArtifactSet and fingerprint the evidence reviewed. Expiry, revocation or evidence changes invalidate
it. Do not infer production approval.
## Consequences
Operators make the first code/data execution boundary explicit and auditable. New revisions and
artifact variants require separate review even when the model name is unchanged.
@@ -0,0 +1,14 @@
# ADR-0014: Static versus locally proven runtime support
Status: accepted (M4)
## Decision
Represent static compatibility and successful local execution as different evidence. Runtime
declarations can establish format/architecture expectations; only a completed probe can establish
local support for an exact environment/profile/node/artifact fingerprint.
## Consequences
Unknown or incomplete evidence remains `unknown`/`requires_probe`; no optimistic compatibility
fallback exists. Driver, image, runtime, artifact or hardware changes make evidence stale.
@@ -0,0 +1,8 @@
# ADR-0015 — Capability-first serving gateway
Status: accepted (M5)
Projects call a central authenticated Gateway with a versioned capability. Concrete model/runtime
selection is server-side. This preserves upgrade isolation, prevents public worker exposure and
provides one QoS, audit and privacy boundary. No cloud or direct-worker fallback is allowed.
@@ -0,0 +1,8 @@
# ADR-0016 — Database-backed GPU lease coordination
Status: accepted (M5)
GPU admission uses persisted leases and a locked accelerator row. Correctness therefore does not
depend on one API process or an in-memory mutex. Server timestamps, expiry and reconciliation make
crashed-request reservations recoverable.
@@ -0,0 +1,8 @@
# ADR-0017 — Explicit load-on-demand residency
Status: accepted (M5)
Long-lived residency and short request leases are separate objects. A typed outbound worker owns one
resident deployment slot, serializes transitions and reuses it within its measured envelope.
Keep-warm limits load thrashing; controlled drain/unload measures allocator-owned reclaim.
@@ -0,0 +1,8 @@
# ADR-0018 — Embedding-space identity is independent of dimension
Status: accepted (M5)
Embedding identity hashes exact artifacts, runtime profile and capability contract. Equal vector
dimension is insufficient evidence of compatibility. Every identity change is `requires_reindex`
and is exposed in native responses.
@@ -0,0 +1,8 @@
# ADR 0019 — Shadow embedding migrations never mutate production aliases
Status: accepted
Embedding changes always build a distinct collection with explicit `embedding_space_id`. Progress
is checkpointed after complete Qdrant batches and validated from canonical chunk fingerprints.
M6 exposes cancel/resume/evaluate but no cutover. This prevents dimension equality, partial writes
or evaluation success from silently becoming production state.
@@ -0,0 +1,7 @@
# ADR 0020 — Evaluation suite revisions are immutable
Status: accepted
Queries, relevance labels/grades, critical flags, metrics, top-K and retrieval configuration form
one content-digested revision. Runs reference that revision permanently. A change creates a new
revision, preserving reproducibility and preventing retrospective baseline changes.
@@ -0,0 +1,8 @@
# ADR 0021 — Project-specific evidence precedes general model claims
Status: accepted
Evidence is ordered project → suite revision → corpus → retrieval configuration → target embedding
space/deployment → environment. ModelForge may report ExampleRAG eligibility from comparable local
runs, but M6 makes no global “best model” recommendation. A later Advisor may consume this evidence
without weakening its provenance hierarchy.
@@ -0,0 +1,30 @@
# ADR 0022 — Bounded serving poll and opaque experiment routes
Status: accepted
## Context
M6 showed a warm Gateway p50 of 1144.46 ms although the model inference itself was much shorter.
The worker polled for jobs and completions with fixed one-second sleeps, and a project evaluator had
no capability-first way to address a non-production profile without learning a concrete deployment.
## Decision
- Job claims use server-side bounded long polling. The wait is capped at five seconds and the worker
renews it at one-second intervals, so shutdown and liveness remain responsive.
- Gateway timing is decomposed into validation, resolution, scheduling, payload persistence,
dispatch, completion transport, result retrieval and worker preprocess/inference/serialization.
Only bounded summaries are retained; request content is never trace data.
- A LAB_READY candidate may receive an opaque `capability-experiment` route. Authentication remains
scoped to `rag.embedding@1`; the caller neither supplies nor receives a model path.
- Experiment deployments are non-production, use an execution approval rather than a production
approval, have their own EmbeddingSpace and cannot change the stable route.
- On a single GPU, the scheduler unloads an inactive resident model before loading another candidate.
It refuses to switch while requests are active and continues to include unmanaged external VRAM
in the budget.
## Consequences
Warm serving no longer inherits a polling floor. Candidate evaluation remains capability-first and
auditable, while the one-GPU implementation avoids unsafe simultaneous loads. A candidate route is
not a canary and provides no production-cutover operation.
@@ -0,0 +1,31 @@
# ADR 0023 — Deterministic Advisor and raw evidence matrix
Status: accepted
## Decision
Model comparisons are first-class immutable evidence snapshots. Each evaluated row references an
existing EvaluationRun; quality metrics and critical-case outcomes are materialized from that run
and its pairwise comparison rather than accepted as caller claims. Operational, resource, migration,
security and provenance evidence remain separate fields. Blocked and unknown candidates remain
visible as blocked or unknown and never receive fabricated metrics.
Advisor recommendations are deterministic. The persisted policy requires project-local evidence
class A for a high-confidence recommendation, treats any critical regression as a hard block,
requires verified supply chain and runtime fit, and requires non-negative quality deltas. A p95
increase above 100% is an operational review ceiling, not a weighted quality score. Policy versions
and rationale are persisted with each recommendation.
The only verdicts are `KEEP_CURRENT`, `PROMOTION_ELIGIBLE`, `PROMOTION_NOT_RECOMMENDED` and
`REQUIRES_MORE_EVIDENCE`. There is deliberately no automatic approval or promotion endpoint.
## Evidence hierarchy
1. A — local project evaluation on the pinned suite/corpus/configuration.
2. B — local generic benchmark or measured preflight.
3. C — independent external benchmark.
4. D — upstream model card or vendor claim.
5. E — downloads, likes or trending data.
Lower evidence cannot overrule project-local critical regressions without a new explicit policy and
new recommendation fingerprint.
@@ -0,0 +1,18 @@
# ADR-0024: Canonical Git and private Gitea baseline
## Status
Accepted — 2026-08-25.
## Decision
The first honest repository commit captures the already verified M7 disk state. No synthetic
milestone history is created. `Jens/ITWorx-ModelForge` on private ITWorx Gitea is canonical;
`master` is the default branch and `m7-baseline` identifies the immutable pre-M8 governance commit.
Force-push and branch deletion are blocked while non-force direct pushes remain compatible with the
existing autonomous workflow.
## Consequences
Baseline provenance and release gates are recorded separately from later logical M8 commits.
Secrets, runtime state and model artifacts remain excluded from history.
@@ -0,0 +1,16 @@
# ADR-0025: Freeze first-stage candidate pools for reranking comparisons
## Status
Accepted — 2026-08-25.
## Decision
Control and reranked results must consume the same immutable ordered candidate pool, identified by
suite case, embedding source, corpus and retrieval configuration. Persist IDs, scores and content
hashes, never text. Top 40 is a ceiling, not permission to pad an in-scope pool.
## Consequences
Reranker gains cannot be caused by retrieval drift. A reranker cannot claim recovery of an item not
present in the pool, and mismatched pools are non-comparable.
@@ -0,0 +1,16 @@
# ADR-0026: First-class retrieval pipeline identity
## Status
Accepted — 2026-08-25.
## Decision
Identity covers embedding space, sparse/fusion digests, optional reranker deployment/configuration
and candidate/output K. Embedding-only and reranked configurations are distinct immutable domain
objects.
## Consequences
Advisor and UI can compare pipeline behavior without conflating it with an embedding deployment.
Reranker addition is behavioral; embedding replacement requires reindexing.
@@ -0,0 +1,16 @@
# ADR-0027: Deterministic Advisor recommendations for retrieval pipelines
## Status
Accepted — 2026-08-25.
## Decision
Advisor derives pipeline metrics from completed fixed-pool runs and applies the same hard gates as
embedding recommendations. Typed target kind, verdict, evidence level, deltas, migration impact and
blockers are persisted. No weighted popularity score or automatic action is allowed.
## Consequences
`KEEP_CURRENT` is a successful evidence result. A future reranker recommendation can be expressed
without implying an embedding-space change or reindex.
@@ -0,0 +1,16 @@
# ADR 0028: Typed multimodal payloads and evaluation families
- Status: accepted
- Date: 2026-08-26
## Context
Embedding-only text contracts cannot safely represent images, documents or audio. Reusing one generic score would also make unlike capability quality incomparable and encourage accidental promotion.
## Decision
Each modality receives a versioned capability contract, bounded input/output models, a specialized offline runtime adapter where needed, and its own typed evaluation family. Binary content crosses the gateway only through a transient payload store and never becomes durable control-plane content. Visual embedding spaces include preprocessing identity. Resource-class labels are advisory; measured byte envelopes drive admission.
## Consequences
Consumers remain model-agnostic and workers remain inaccessible. A new capability requires explicit validation and evaluation work, but it can be added without redesigning the gateway, scheduler, provenance model or advisor. Cross-family scores and silent visual-vector reuse are rejected by construction.
@@ -0,0 +1,25 @@
# ADR 0029 — NVML truth and multi-resident placement
Status: accepted
## Context
The M5–M9 scheduler safely serialized one resident deployment, but that prevented proven small
capabilities from coexisting. Naively subtracting both NVML-used and managed residency would double
count memory; trusting absent telemetry as zero would overcommit the GPU.
## Decision
NVML used is physical truth. Managed residency is attribution within that observation, and only
unmaterialized lease bytes are additionally reserved. Unknown/stale telemetry fails closed. A
single versioned planning function drives dry-run and actual admission under a database accelerator
lock. The runtime worker may retain multiple exact deployment identities. Eviction is limited to
managed idle residency and recovery adopts only an exact immutable identity report.
## Consequences
GPU Node can serve multiple capabilities when current observations and measured envelopes prove fit,
without weakening the 1 GiB minimum reserve. External workloads remain visible but unmanaged.
Conservative rejections may reduce utilization during telemetry uncertainty or pressure; that is an
intentional safety result, not a capability-health failure.
@@ -0,0 +1,20 @@
# ADR-0030: Operational project bindings and immutable project-fit evidence
## Status
Accepted in M11.
## Decision
An external consumer is operational only through a scoped service identity bound to one real
`ProjectBinding`. Project environment, purpose and server-owned QoS are attributes of that identity.
Project-fit conclusions are separate immutable evidence records bound to the project contract and,
when applicable, the exact deployment.
## Consequences
Planning manifests cannot masquerade as live usage. Consumers cannot select a model, runtime, worker,
node or priority. Credential rotation preserves binding identity while revoking the old secret.
Project-fit evidence can explain readiness but cannot promote. Usage and “Why Installed” can be
derived from domain relations without storing content or adding project-specific gateway routes.
@@ -0,0 +1,64 @@
# ADR-0031: Deferred owner-photo validation and visual evidence classes
## Status
Accepted in M11 on 2026-08-26.
## Context
M11 proved ExampleVision's capability-first shadow integration, complete reference index, scoped
credentials, privacy boundary, Gateway/Scheduler/GPU Node execution and bounded QoS. It did not receive
an independently reviewed owner-photo dataset. Treating that external data dependency as an
unbounded engineering blocker conflated integration readiness with project production fitness.
Catalog artwork cannot prove camera robustness. Public physical captures can improve robustness
evidence, but their licenses, provenance and labels vary and they cannot substitute for ExampleVision
production captures when calibrating automatic decisions.
## Decision
Visual project evidence has one immutable class:
- `OWNER_PHOTO`: physical-card captures from the target user/project context, independently labelled;
- `PUBLIC_PHYSICAL_CAPTURE`: licensed, provenance-reviewed physical-card captures from a public
source;
- `CATALOG_REFERENCE`: official/reference artwork or derivatives used only for index, embedding and
technical route validation.
`ProjectFitEvidence` also records separate immutable statements for engineering integration,
project-production recommendation, production validation and production action. Engineering may be
`PASS` while project production fit remains `REQUIRES_MORE_EVIDENCE` and production action remains
`NONE`.
M11 engineering closure does not require owner photos. The separate
`POKEVAULT_VISION_PRODUCTION_VALIDATION` gate does. It can be `SATISFIED` only by non-empty,
independently reviewed `OWNER_PHOTO` evidence with zero critical errors and blockers and an eligible
project-fit recommendation. Public physical captures and catalog references are rejected as
production-gate substitutes.
The M11 release taxonomy is:
- `PASS`;
- `PASS WITH DEFERRED EXTERNAL VALIDATION`;
- `PASS WITH DOCUMENTED CANDIDATE LIMITATIONS`;
- `BLOCKED`.
M11 uses `PASS WITH DEFERRED EXTERNAL VALIDATION` while owner-photo validation is absent.
## Consequences
- M11 can close after its engineering gates pass without claiming real-photo accuracy.
- `VISION_AUTO_ACCEPT=false` and ExampleVision human confirmation remain mandatory.
- Existing Vision evidence remains immutable and `REQUIRES_MORE_EVIDENCE`.
- No public dataset result can activate automatic recognition or satisfy production validation.
- A later production proposal must bind owner-photo dataset revision, independent ground truth,
leakage checks, critical/false-confident results, frozen calibration and explicit approval.
- Advisor and UI expose engineering, project-fit, production-gate and action state separately.
## Rejected alternatives
- Keeping M11 indefinitely blocked: this makes an external manual data-collection activity part of
engineering completion despite a proven shadow integration.
- Treating catalog artwork as real-photo evidence: this hides the camera-domain gap.
- Treating public captures as owner photos: this loses project provenance and calibration relevance.
- Enabling auto-accept from public data alone: this bypasses the project-specific production gate.
@@ -0,0 +1,31 @@
# ADR-0032: Evidence-bound production approvals
## Status
Accepted in M12 on 2026-08-26.
## Context
Technical probes, Advisor recommendations and engineering integration each prove useful but distinct
facts. Reusing their labels as production permission would silently reinterpret stale evidence and
collapse platform readiness into project fitness.
## Decision
Production permission is a first-class ApprovalRequest bound to an immutable ApprovalPolicy revision
and a fingerprinted snapshot of exact declared IDs plus database-resolved facts. Policies are
revisioned. Requests expire, can be revoked and become stale when referenced facts change. Default
production policy hard-gates security, license, evaluation, resource readiness, rollback and project
validation. Approval remains separate from execution.
## Consequences
M11's engineering PASS can coexist with a blocked ExampleVision production request. Old approvals retain
their interpretation and cannot silently authorize changed artifacts, runtimes, fit or scheduler
evidence. More records and explicit operator steps are required, but every permission is explainable.
## Rejected alternatives
- Treating `LAB_READY` or `PROMOTION_ELIGIBLE` as permission: conflates evidence with authority.
- Storing only a model/capability name: loses exact provenance.
- Re-evaluating old requests under the latest policy: silently changes historical meaning.
@@ -0,0 +1,30 @@
# ADR-0033: Promotion plans and rollback snapshots
## Status
Accepted in M12 on 2026-08-26.
## Context
Approval alone does not describe blast radius, canary semantics, resource placement, migration or
restoration. A single-click approval-and-swap cannot be reconciled safely after races or restart.
## Decision
An approved request permits an immutable PromotionPlan. Execution journals a separate idempotent
LifecycleOperation and captures an immutable rollback snapshot before activation. Subject versions
provide compare-and-swap protection, and the database enforces single production-stable deployment.
Successful canary requires a second explicit review; failures and incomplete restart stages roll back
conservatively.
## Consequences
Requester, approver and executor are auditable even for one owner. Plan changes create new plans.
Failed rollback becomes manual intervention, never false stable. Embedding reindex and schema-breaking
plans are representable but not auto-executed by M12.
## Rejected alternatives
- Mutating deployment rows directly: loses intention and recovery state.
- Automatic promotion after canary: removes the explicit production decision.
- Reconstructing rollback from current rows: current truth may already be partially changed.
@@ -0,0 +1,28 @@
# ADR-0034: Retention and dependency-aware cleanup
## Status
Accepted in M12 on 2026-08-26.
## Context
Unused-looking model bytes may still back a deployment, rollback, evaluation, project, migration or
another node. Immediate deletion after promotion destroys recoverability and provenance.
## Decision
RetentionPolicy is revisioned; prior stable targets receive a minimum rollback window. Deprecation,
archive and physical removal are separate. Cleanup always begins with a dependency-digest dry-run,
requires explicit operator execution and rechecks dependencies transactionally. Location removal is
node-specific and records prior facts; metadata/provenance remain.
## Consequences
Storage recovery may be delayed by valid dependencies or retention. Cleanup races return stale/409
instead of deleting. Multiple node copies are reconciled independently and audit remains queryable.
## Rejected alternatives
- Age-only automatic cleanup: cannot see active or rollback dependencies.
- Cascading deletes: erases evidence and makes blast radius implicit.
- Treating one removed location as global deletion: is incorrect in a multi-node registry.
@@ -0,0 +1,16 @@
# ADR-0035: Typed migration adapters and immutable plans
## Status
Accepted in M13 on 2026-08-26.
## Decision
ModelForge stores immutable, evidence-bound migration intent and accepts results only for a closed,
fingerprinted adapter operation set. Project data planes implement concrete transformations. No plan
contains executable SQL, shell or Python.
## Consequences
Adding a backend requires trusted adapter code and tests. Core stays storage-neutral, source/target
semantics remain reproducible and capability clients cannot obtain migration authority.
@@ -0,0 +1,16 @@
# ADR-0036: External-truth cutover and reconciliation
## Status
Accepted in M13 on 2026-08-26.
## Decision
Cutover uses a durable operation journal, immediate CAS/source check and adapter-owned atomic
primitive. Database commit follows verified external identity and health. Startup reconciliation
requires measured external truth and chooses commit, rollback or manual intervention conservatively.
## Consequences
ModelForge does not pretend to provide a distributed transaction. Crash windows are explicit,
idempotent and auditable; ambiguous state cannot silently become production.
@@ -0,0 +1,16 @@
# ADR-0037: Migration validation and rollback retention
## Status
Accepted in M13 on 2026-08-26.
## Decision
Cutover requires an immutable validation snapshot. Technical LAB eligibility and project-production
eligibility are separate. The exact prior external identity is snapshotted before switch and remains
cleanup-blocking through the configured rollback window.
## Consequences
Completeness alone cannot promote a model. Isolated mechanics can be rehearsed despite documented
project evidence gaps, without implying production fitness or sacrificing rollback evidence.
@@ -0,0 +1,17 @@
# ADR-0038: Versioned SLO and error-budget model
## Status
Accepted in M14 on 2026-08-27.
## Decision
Separate immutable SLI definitions from versioned SLO policies and immutable evaluation snapshots.
Only production policies receive error budgets. Minimum population and freshness are evaluated
before health; short and rolling-window burn rates remain distinct.
## Consequences
Threshold changes cannot rewrite historical meaning. LAB evidence cannot masquerade as production
reliability. A serving capability can consume budget without being misreported as unavailable, and
too little evidence remains explicitly `INSUFFICIENT_DATA`.
@@ -0,0 +1,17 @@
# ADR-0039: Deduplicated alert lifecycle
## Status
Accepted in M14 on 2026-08-27.
## Decision
Use immutable versioned rules and deterministic logical fingerprints with pending, firing,
acknowledged, resolved and maintenance-suppressed states. Every transition is durable; M14 performs
no automatic destructive remediation.
## Consequences
Polling does not produce alert spam, acknowledgement cannot erase evidence, recovery stays visible
and maintenance cannot silently disable monitoring. Lightweight incidents claim causality only for
evidenced node/downstream relationships.
@@ -0,0 +1,17 @@
# ADR-0040: Bounded capacity telemetry retention
## Status
Accepted in M14 on 2026-08-27.
## Decision
Sample the existing authoritative hardware/scheduler state at minute granularity, retain raw
snapshots for seven days and hourly aggregates for 90 days. Forecast only with at least 12 samples
spanning one hour. Monitoring persistence is not a serving dependency.
## Consequences
ModelForge gains explainable GPU/storage trends without a second NVML collector or an unbounded
time-series table. Sparse/stale history is reported honestly, and external workloads remain observed
but unmanaged.
@@ -0,0 +1,31 @@
# ADR-0041: Authoritative versus rebuildable recovery state
## Status
Accepted in M15 on 2026-08-27.
## Decision
Classify every persisted state source as `AUTHORITATIVE`, `REBUILDABLE`, `EPHEMERAL`, `EXTERNAL` or
`SECRET` in a versioned `RecoveryPolicyRevision`, and enforce the classification in the contract:
authoritative state may not declare a manifest-only or absent backup and must state an RPO,
ephemeral state may not claim a payload backup, external state must be marked as a dependency,
secret state must name a secret recovery class, and only rebuildable state may be rehydrated.
Partition the database itself along the same line. Fingerprinted history is compared across a
restore; `CURRENT_TRUTH_TABLES` — NVML telemetry, GPU leases, residency, inventory runs, storage
volume states — is cleared during reconciliation and re-measured by a running control plane. A test
asserts the two sets together cover the whole schema.
## Consequences
Recovery cost is proportional to what is genuinely irreplaceable: a 5 MB encrypted control-plane
dump protects the provenance, lifecycle, migration, approval and audit truth, while 8.1 GB of model
weights are protected by exact-revision manifests instead of being copied.
A restored control plane cannot present a stale GPU lease or a dead NVML reading as present-day
fact, and the `READY` gate refuses to pass while any current-truth row survives. External projects
are recorded as dependencies rather than silently claimed as backed up.
The cost is that a new milestone must classify every table it adds. The invariant test makes that a
build failure rather than a silent recovery gap.
@@ -0,0 +1,35 @@
# ADR-0042: Immutable verified backup sets
## Status
Accepted in M15 on 2026-08-27.
## Decision
Make `BackupSet` immutable recovery identity, sealed by `immutable_at` once written, carrying the
ModelForge version and commit, the canonical repository and reference, the Alembic revision, the
PostgreSQL server identity and tool version, the destination, the hashed manifest and payload, the
included and excluded asset classes and the encryption identity.
Treat verification, not creation, as the gate. Only `VERIFIED` is restore eligible, and verification
checks the manifest hash, manifest-to-journal agreement, the presence of a database payload, every
object's size and hash at an allowlisted path, decryption against the recorded plaintext hash, and
archive readability. Anything else fails closed with a typed code.
Protect the last known-good backups from retention unconditionally, and reconcile any backup
interrupted mid-write to `FAILED`.
## Consequences
A backup cannot look usable because a file exists. A single flipped byte in a 5.4 MB encrypted
payload, a one-integer edit to a manifest entry, a wrong encryption key and a mid-write interruption
were each caught in live rehearsal with `HASH_MISMATCH`, `MANIFEST_HASH_MISMATCH`,
`DECRYPTION_FAILED` and `MANIFEST_INCOMPLETE`, and none of them could become a restore plan.
Immutability across several flushes required loosening the immutability guard to permit exactly the
sealing flush — a backup is planned, written and only then stamped — while still rejecting every
later change to identity fields.
The platform can never retain zero recovery points because a timer expired, and a backup can never
contain a record of itself as complete, so a restored plane consistently reports the backup it came
from as not restore eligible.
@@ -0,0 +1,37 @@
# ADR-0043: Artifact rehydration and disaster-recovery boundaries
## Status
Accepted in M15 on 2026-08-27.
## Decision
Recover rehydratable artifacts by redownloading their exact upstream revision through the existing
acquisition plane — download plan, quarantine, hash verification against provenance, atomic
promotion — rather than by copying weights into every backup. A floating revision is never used, and
an artifact set without both an upstream repository and a resolved commit is classified
`NON_REHYDRATABLE` and requires a payload backup.
Treat a rehydrated artifact as new material: it re-enters quarantine, is re-checked against the
security policy, keeps `trust_remote_code=false` and carries
`static_checks_passed_unapproved` rather than inheriting its predecessor's approval.
Draw the disaster-recovery boundary explicitly. ModelForge restores only into a database that is not
its own, never writes to ExampleRAG, ExampleVision, Ollama, Plex, Tdarr or Gitea, and does not claim to
protect its own Git host. Restore execution against a production target requires an explicit mode
and an explicit deployment flag; the destructive half of recovery lives in the operator CLI and
runbooks rather than in a remote API call.
## Consequences
Backups stay small and fast while a 247 MB artifact set was recovered in 24.2 seconds with all seven
digests matching provenance, into a disposable location, leaving the 75 production artifact
locations untouched.
Recovery cannot be used to launder an unapproved artifact into an approved one, and an unreachable
upstream produces `ARTIFACT_REHYDRATION_BLOCKED` instead of a false claim that the platform is
recovered.
The boundary is a real constraint: a genuine loss of a non-rehydratable or derived artifact needs a
payload backup that must be configured deliberately, and full protection of the Git host remains
platform operations rather than a ModelForge feature.
@@ -0,0 +1,39 @@
# ADR-0044: Bounded fault injection and executable invariants
## Status
Accepted in M16 on 2026-08-27.
## Decision
Inject every fault from outside the product — the container runtime, existing rehearsal seams, real
credentials, real load — and give ModelForge no chaos endpoint, no command route and no way to run
a shell. Subprocess use stays allowlisted to the recovery plane, with argv built as a list and
never a string, and static tests enforce all of it.
Express the platform's safety properties as fifteen read-only invariant queries over authoritative
state rather than as prose. Run them before a fault, after it, and after recovery. A scenario that
survives its fault but breaks an invariant reports `INVARIANT_VIOLATED` rather than passing, and a
scenario that cannot restore the platform reports `RECOVERY_FAILED` rather than leaving the
environment dirty.
Where a property is already enforced by a partial unique index, keep the index as the primary
defence and treat the invariant as defence in depth for a bypassed guard.
## Consequences
Fault injection cannot become an attack surface: there is nothing in the product to call. The cost
is that some faults can only be reached with runtime access, which is the correct trade for a
control plane.
Invariants are usable during an incident because they never write, never call an external system
and never read the observability database — the case where monitoring itself has failed is exactly
when the question "is state still sound?" matters most.
Each invariant carries a negative test that constructs its violation, because a check that never
fires reports safety it did not verify. Two of the M16 scenarios found real product defects and one
scenario was itself wrong first, reporting that a heartbeat kept advancing during an outage it was
not actually causing; recording observations rather than only pass/fail is what surfaced that.
Every new milestone must classify the tables it adds, or the fingerprint and invariant coverage
tests fail the build.
@@ -0,0 +1,41 @@
# ADR-0045: Chaos-safe reconciliation semantics
## Status
Accepted in M16 on 2026-08-27.
## Decision
Every reconciliation path converges rather than accumulates, and each subsystem declares which of
three answers it gives after an interruption:
```text
resolve the safe outcome is knowable from local state; apply it exactly once
report the safe outcome depends on external truth; surface it and stop
fail closed the artefact cannot be trusted; mark it unusable
```
Lifecycle resolves: an incomplete operation rolls back from its immutable snapshot, once, and a
second start changes nothing. Migration reports: an interrupted cutover stays in its intermediate
stage because external alias truth cannot be inferred after a crash. Backups fail closed: a backup
interrupted mid-write can never become restore eligible, and a restore interrupted mid-phase
requires operator review rather than resuming.
Single-use claims are atomic conditional updates whose row count decides the winner, not
read-then-write checks. Idempotency keys are unique in the database, not in a queue, so a retry
storm converges on one row regardless of which component retried.
## Consequences
A restart storm is uneventful: four consecutive control-plane restarts under load produced no
authoritative drift, and a Redis outage produced no duplicate serving job.
The cost is that some recoveries stop and wait for a human. That is deliberate — a migration
cutover that guesses is how a platform ends up serving two embedding spaces, and an operator
reconciling through the typed adapter is cheaper than a silent split.
"Exactly once" has to be scoped to the operation, not the action class. M15 shipped a reconciliation
marker keyed on the audit action alone, which meant a backup carrying an earlier restore's
reconciliation event made every later restore believe it had already reconciled. The marker is now
per-operation and the readiness gate additionally refuses to pass while stale current truth
survives.
@@ -0,0 +1,39 @@
# ADR-0046: v1 security release gate
## Status
Accepted in M16 on 2026-08-27.
## Decision
Treat the security review as a source of build-enforced properties rather than a point-in-time
report. Every conclusion the M16 review reached by hand is encoded as a test: no shell execution,
no arbitrary execution primitive, subprocess use allowlisted, no command route, AES-256-GCM from a
reviewed library with a fresh nonce per chunk and fail-closed decryption, no privileged mode or
Docker socket, no floating dependency specifier, datastores bound to loopback, capabilities dropped,
and a console image that serves a build rather than a development server.
Publish nothing on a network interface that is not authenticated for it. PostgreSQL, Redis and the
operator console default to loopback; the API is published deliberately because every admin route
is operator-authenticated.
Inventory what shipped, not what was intended: read the Python SBOM from the built images, bind each
image to its source commit, and classify every advisory with evidence rather than counting them.
## Consequences
The most serious finding of M16 was configuration, not code: the control-plane database was
published on every interface behind a development password, exposing provenance, credential hashes
and the audit trail to the LAN. A test now fails if that binding returns.
Encoding review conclusions as tests means M17 cannot quietly reintroduce them while packaging, and
a future reviewer inherits the reasoning rather than having to rediscover it.
The cost is friction: adding a subprocess call, a new route segment, or a dependency without a
version now requires either a deliberate allowlist change or a failing build. That friction is the
point.
Some findings are correctly accepted rather than fixed. `pip` carried archive-extraction advisories
and never runs at runtime, but leaving a known-vulnerable installer in a release image makes every
future scan noisier, so the build upgrades it — a cheap fix is preferred to a standing exception
even where the risk is not reachable.
@@ -0,0 +1,30 @@
# ADR-0047: terminal audited node decommission
## Status
Accepted after v1.0 on 2026-08-28.
## Decision
Represent permanent compute-node removal as an immutable tombstone on `ComputeNode` plus one
`NodeDecommissionOperation`, never as a hard delete. Require an authenticated preview/execute
protocol bound to an optimistic node generation and a deterministic dependency digest. Recheck all
blockers in the execution transaction, lock the dependency graph on PostgreSQL, and provide no
force override.
Revoke credentials and remove only current, rebuildable truth. Preserve every identity and record
needed to explain acquisition, validation, runtime, deployment, serving, capacity and audit history.
Block ordinary enrollment and every other known mutation path from resurrecting the tombstone.
## Consequences
Foreign-key `CASCADE`, `SET NULL` and `RESTRICT` semantics no longer determine whether node history
survives an operator action: the row is retained deliberately. Operators get an explicit inventory
of what is blocked, changed and preserved before execution, and retries return the original result.
The transition takes broad, short-lived PostgreSQL table locks. Decommission is rare and safety is
more important than maximizing concurrent scheduling throughput. New node-referencing tables must
be added to the preview, lock set, cleanup classification and terminal invariant before release.
Recovery from an outage and permanent decommission remain distinct. Reusing a decommissioned
persisted identity is refused until a separately designed explicit recovery lifecycle exists.