Core model contracts (v0.3 alpha)
Start here when changing the backend, API, UI, or a plugin. This page describes
implemented contracts on main; it does not describe every capability planned
for Consize. Kubernetes Deployments with configured Prometheus evidence are the
current working optimization path. Other resource types can be represented
without being discoverable or actionable.
Follow one change
flowchart LR
R[Resource: stable provider identity] --> E[Evidence: immutable measurements]
R --> P[Recommendation: proposed change]
E --> P
P --> A[Action: one governed attempt]
A --> J[Job: resumable worker state]
J --> V[Verification: before and after evidence]
V --> O[Success, rollback or manual intervention]
A --> H[Append-only events]
J --> H
Discovery updates a resource, not its old recommendations. Evidence records explain the measurements behind a proposal. A recommendation records the proposal; it does not grant permission to execute. An action captures one request, its policy decision and results. A worker job resumes the approved workflow. Verification compares recorded observations. Events preserve what happened.
Example: payments/checkout-api is observed in example-cluster. A collector
records memory usage. An algorithm proposes a smaller memory request. A dry-run
action records a plan. A later approved attempt must still pass authorization,
policy, preflight, baseline and rollback checks. Successful health verification
is not proof of reduced billing.
The following rules are normative for this alpha. Files under examples/core-model are synthetic illustrations, not live measurements or executable change plans.
Resource: what exists
resource.Resource represents one provider
object. resource.Registry owns its writes,
lookups, lifecycle and reconciliation.
For a new resource, supply:
- Identity:
provider,account,region,type,provider_resource_id. - Classification:
name,environment,owner,criticality.
environment is production, staging or development; criticality is low,
medium or high. The current registry requires a nonempty owner. An unowned
resource in the UI's design fixtures does not mean the backend accepts it.
The store normalizes a create/upsert input before validation:
schema_versiondefaults to 1; an omittedidis derived from identity.lifecycle_statedefaults toactive;support_statusis derived by core.labels,metadataandcurrent_statedefault to empty objects.observed_atdefaults to the registry clock. Initialfirst_seen_at,last_seen_atandcreated_atare populated;updated_atis store-owned.source_plugin_idcan be empty for explicitly registered resources; discovery supplies it.lifecycle_observed_atis a separate presence/lifecycle clock.monthly_costis optional descriptive data; zero does not prove a free resource.
Identity is the normalized five-coordinate tuple. New canonical IDs encode it as
resource:v1:<provider>:<account>:<region>:<type>:<provider_resource_id> with
URL-escaped components. Never build IDs by joining display names.
Identity cannot change under an existing ID. Name, environment, owner, criticality,
labels, metadata, current state and cost may change. Store updates preserve
creation/first-seen times. An existing source cannot silently be replaced by a
different source. Provider-specific fields belong in the extension objects;
credentials do not. fully_supported describes the type's implemented support,
not plugin health, telemetry availability or permission to automate.
See resource fields and support, registry operations, identity ADR, and resource tests.
Evidence: why a measurement is trustworthy
evidence.Record is immutable after sealing.
A producer supplies resource_id, plugin_id, signal, query, kind,
aggregation, unit, original_unit, scale, value, sample_count,
window_start, window_end, collected_at, fresh_until and quality.
Seal supplies schema_version and the content-addressed id.
parents is required for derived evidence and otherwise optional. extensions
is optional bounded string metadata. Raw, aggregated and derived records use the
same provenance rules; a percentile summary is not a raw sample stream.
valid,partialandstalerequire a number and at least one sample.missing,invalidandunavailablerequirevalue: null. Measured zero is a number, not missing data.window_start < window_end <= collected_at <= fresh_until.- A valid digest proves content consistency, not authenticity. Freshness and
consumer-required units/quality are checked separately.
Usablenever rewrites historical quality just because time passed.
New evidence produces a new ID. Embedded recommendation snapshots and their
ordered evidence_refs must agree exactly, belong to the same resource and
survive restart. Reference-only legacy records remain readable; they do not
establish usable provenance for a new built-in decision.
See quality, normalization and retention, evidence tests, and snapshot persistence tests.
Recommendation: what is proposed
store.Recommendation freezes a proposal for
one resource. The store requires an existing resource_id, nonempty plugin_id,
action_type, algorithm_id and evidence_refs. Here plugin_id names the
proposed action plugin; evidence records name their own measurement plugins.
For typed evidence, also supply algorithm_version and evidence_snapshot with
one validated record per ordered reference. Keep title, summary, current,
proposed, parameters, human-readable evidence, confidence, risk and
policy_id with the proposal so reviewers can understand it. The store's minimum
write validation does not guarantee that every possible imported proposal is
safe, actionable, or sufficiently explained.
The store assigns id, schema_version, created_at and updated_at. Creation
starts at pending. recommendation_type defaults to action_type and an omitted
algorithm version defaults to 1 only for legacy/reference-only inputs. Empty
proposal objects become {}. estimated_savings_monthly and savings_estimate
retain the estimate; cost_estimate is optional pricing provenance. Savings
classification must not be confused with the action's health result.
savings_estimate contains classification, amount_monthly, and optional
currency, source and calculated_at.
expires_at is optional and must be in the future when supplied at creation.
status_reason and superseded_by record lifecycle decisions. An expiry timestamp
alone is not the same thing as a recorded expired transition. New actions reject
expired timestamps even before a lifecycle transition has been written.
Proposal content is immutable after creation. Lifecycle methods may change
status, status_reason, superseded_by and updated_at; the worker can also
record its captured policy ID. New evidence creates a new recommendation.
Supersession must reference a replacement for the same resource.
Action: one attempt to realize a proposal
store.Action requires an existing
recommendation_id, matching resource_id, plugin_id and action_type, plus
remediation_path, mode, idempotency_key and requested_by. Supported store
modes are dry_run and approved. A nonempty path string is not proof that an
execution implementation exists for that path.
plugin_version, correlation_id, approved_by and parameters capture request
context when supplied. The store generates correlation when absent and defaults
parameters to {}. It assigns id, schema_version, status: requested,
requested_at and updated_at.
The request identity, plugin/version, path, mode, idempotency key, correlation,
requester, parameters and captured policy_decision cannot change through a
transition. Governed transitions update status, approver and lifecycle timestamps,
plan_result, execution_result, failure_code and failure_message.
approved_at, started_at and finished_at record the corresponding lifecycle
milestones. Optional timestamps may serialize as a zero date in existing Go JSON;
check the status and actual timestamp rather than treating field presence as proof.
The globally unique idempotency key reuses an existing matching request. A changed recommendation/resource/plugin/action type/mode with that key is rejected; callers must not reuse a key to ask for a different proposal. Idempotency grants no approval. A dry-run plan is not an execution result.
See exact lifecycles and savings terms, contract tests and the separation ADR.
Jobs, verification and history
A store.Job is worker recovery state, not a
second recommendation. It keeps id, action_id, recommendation_id, the
captured resource, plan, baseline, verification configuration, policy and
actor. Creation checks the recommendation/action/plan relationship and reserves
the resource against another unresolved job.
The worker updates state, attempts, next_run, window_start, deadline,
last_error, updated_at, verification_result and plugin_result. Job states
such as prepared, applying, waiting_rollout and rollback_verifying are not
interchangeable with public action states. manual_intervention stops ordinary
processing but continues to reserve the resource.
verification.Result contains
status, reasons, before, after, checked_at and thresholds. Status is
passed, failed or inconclusive. Missing or mismatched evidence cannot become a
successful verification. These results remain in jobs and historical action events.
store.ActionEvent is the compatibility action
history: generated id/created_at, resource/plugin/action identity, optional
action_id and recommendation_id, actor/mode/result/message, parameters, captured
policy decision, and optional plan/plugin/verification results. It is append-only.
GET /api/actions returns this history; GET /api/action-records returns attempts.
The broader audit subsystem records discovery, evidence and other events as well. Use its event contract and query API rather than assuming the action-event feed contains every audit record. See worker recovery tests.
Registry and persistence boundaries
The registry is a durable inventory, not a provider deletion API. Stale means presence is uncertain; deleted is a retained tombstone. A complete successful scan may mark absent objects stale within its exact provider/account/region/type/source scope. Confirmed deletion must be explicit. Failed or partial scans cannot safely reconcile absence. The current discovery runner upserts only; scheduled complete scan reconciliation is not yet wired into it.
Reads detach mutable values. Listing uses live ID-ordered keyset pages, not a frozen snapshot across requests. Legacy IDs are preserved during canonical rediscovery. No automatic tombstone or evidence retention policy removes historical references.
Memory is ephemeral. The JSON-file store is for one local process; PostgreSQL is the shared durable backend. A failed write must return an error, never successful state. Review persistence guarantees and migrations and registry concurrency details before changing a write path.
Entity schema_version, JSON-file state version and PostgreSQL migration number
are separate versions. Never infer one from another. Existing migration files are
immutable; add the next migration. Unsupported newer durable versions, changed
migration checksums and invalid references fail startup. Old events must not be
reinterpreted as approvals or successful actions. Public field changes must update
OpenAPI and generated declarations together and pass
compatibility CI.
Examples and review
Read these in order. All are synthetic and validated by the model documentation tests:
- Kubernetes registration input.
- Normalized resource.
- Sealed evidence.
- Recommendation store input.
- Dry-run action store input.
The query in the evidence example illustrates provenance shape, not a recommended
production PromQL configuration. The action input illustrates the store contract,
not an HTTP command or a complete executable plan. Its recommendation ID 1
assumes the first proposal in an empty example store; real callers use the
returned recommendation ID. Historical sample timestamps
are fixed: the examples do not constitute fresh evidence today.
The RDS input demonstrates
model compatibility only. Normalization yields model_only. It does not
install RDS discovery, metrics, a recommender or an action implementation.
Before approving a model change:
- Check identity, immutability, time boundaries and reference ownership against the linked validators and tests.
- Check lifecycle names against store transitions; separate resource, action, recommendation, job and verification states in API/UI copy.
- Validate the examples and local links with
go test ./internal/modeldocs. - Confirm model-only examples are not described as runnable integrations.
- Have a maintainer review any new lifecycle or compatibility claim. That review remains a PR approval requirement, not something a documentation test can grant.