Computed object fields¶
The Atlas example includes the Server
class with a shared monthly_cost field. Read Server/web-01 with
include=computed: its compute cost of 45 and storage cost of 5 produce 50,
without changing raw object data.
Hubuum can derive response-only values from the raw JSON in one object's
data document. Computed fields are deterministic and cannot read relations,
other objects, database state, time, configuration, or external services. A
computed field cannot reference another computed field.
Definitions are class-bound and use one of two visibility scopes:
- Shared definitions belong to a class. They are evaluated for every object in that class and are returned to principals that can read those objects.
- Personal definitions belong to one human user and one class. They are evaluated only for their owner. Service accounts cannot create or receive personal fields.
Shared and personal keys occupy separate maps, so a personal key never
overrides a shared key. Computed values never become part of canonical object
data, object history, or object update events.
Operations and result types¶
Definitions use semantics version 1 and a closed operation catalog.
| Operation | Behavior | Allowed result type |
|---|---|---|
first_non_null |
First resolved value that is neither missing nor JSON null |
Any declared type matching the result |
sum |
Sum of resolved numeric operands | number or integer |
average |
Arithmetic mean of resolved numeric operands | number or integer |
min |
Smallest resolved numeric operand | number or integer |
max |
Largest resolved numeric operand | number or integer |
all_present |
Whether every path resolves to a non-null value | boolean |
any_present |
Whether at least one path resolves to a non-null value | boolean |
count_present |
Number of paths that resolve to non-null values | integer |
all_present_and_equal |
Whether at least two paths are all present and strictly equal | boolean |
Numeric aggregates ignore missing and null operands. An aggregate with no
numeric operands returns null, including sum. A present non-numeric operand
produces a field-level non_numeric_operand error.
Equality is recursive and type-aware. Numbers compare by numeric value, object key order is ignored, and array order is significant. Strings and booleans compare exactly. No coercion, trimming, case folding, or Unicode normalization is performed.
Paths, keys, and fixed limits¶
Paths use RFC 6901 JSON Pointer syntax relative to the root of object data.
The empty pointer selects the document root. ~0 represents ~, ~1
represents /, and array indexes cannot contain a leading zero. Missing and
null values are distinct from present falsy values such as false, 0, "",
[], and {}.
The release-owned limits are fixed so the same definition has the same semantics on every replica:
| Limit | Value |
|---|---|
| Shared definitions per class | 32 |
| Personal definitions per user and class | 16 |
| Paths per definition | 16 |
| Pointer length and tokens | 512 bytes and 32 tokens |
| Field key | [a-z][a-z0-9_]{0,63} |
| Label and description | 128 bytes and 2,048 bytes |
| Input per evaluation | 1 MiB |
| Traversed nodes per field | 10,000 |
| Work units per shared or personal scope | 50,000 |
| Output per field and scope | 64 KiB and 256 KiB |
Numbers use up to 34 significant decimal digits, exponents from -308 through
308, and round-half-to-even arithmetic. Values outside that range produce a
field-level numeric_out_of_range error. There is no float or string coercion.
Definition validation rejects unknown operations, unknown operation members,
duplicate pointers, invalid arity, incompatible result types, and unsupported
semantics versions. Runtime data errors set that field's value to null and
add an entry under errors; they do not reject the raw object write or the
entire response page.
Stable runtime error codes are:
input_too_largenon_numeric_operandnon_integer_resultresult_type_mismatchnumeric_out_of_rangeevaluation_limit_exceededresult_too_large
Managing shared definitions¶
Shared management is class-bound:
GET /api/v1/classes/{class_id}/computed-fields
POST /api/v1/classes/{class_id}/computed-fields
PATCH /api/v1/classes/{class_id}/computed-fields/{field_id}
DELETE /api/v1/classes/{class_id}/computed-fields/{field_id}
If-Match: "<opaque-etag-from-point-response>"
POST /api/v1/classes/{class_id}/computed-fields/preview
POST /api/v1/classes/{class_id}/computed-fields/rebuild
Reading definitions requires ReadClass. Creating, changing, deleting,
previewing, or rebuilding shared definitions requires UpdateCollection for the
class's current collection. Clients do not choose the permission used by the
server.
PATCH and DELETE use the canonical point response's opaque ETag in an
If-Match header. A stale validator
returns 412 Precondition Failed. A value-affecting change atomically updates the
definition, increments the class evaluation revision, marks it rebuilding, and
queues a bounded internal reindex task. Label-only and description-only changes
do not invalidate materialized values.
The rebuild endpoint reuses an active task for the current evaluation revision.
Use the returned active_task_id with the task API to inspect progress. One
rebuild transaction processes
HUBUUM_COMPUTED_REINDEX_BATCH_SIZE objects, which defaults to 100 and must
be between 1 and 1000.
Managing personal definitions¶
Human users manage their definitions through the self API:
GET /api/v1/iam/me/computed-fields?class_id={class_id}
POST /api/v1/iam/me/computed-fields
PATCH /api/v1/iam/me/computed-fields/{field_id}
DELETE /api/v1/iam/me/computed-fields/{field_id}
If-Match: "<opaque-etag-from-point-response>"
POST /api/v1/iam/me/computed-fields/preview
Creating, changing, and previewing a personal definition requires ReadClass.
An owner who later loses class access can still list and delete the inaccessible
definition. Personal definitions are evaluated on read and are never fanned out
during object writes. User anonymization explicitly removes them.
Preview accepts a typed definition and exactly one of an accessible object_id
or explicit sample data. Personal preview also requires class_id. Preview
does not persist a definition or computed result.
Reading computed values¶
Object reads opt in with include=computed:
GET /api/v1/classes/{class_id}/{object_id}?include=computed
GET /api/v1/classes/{class_id}/?include=computed
The response keeps derived values outside raw data:
{
"id": 123,
"data": {
"inventory": {
"hostname": "server-01"
}
},
"computed": {
"shared": {
"revision": 7,
"materialization_stale": false,
"values": {
"display_name": "server-01"
},
"errors": {}
},
"personal": {
"values": {},
"errors": {}
}
}
}
Enriched responses use Cache-Control: private, no-store because personal
identity and definition state can affect them. Service-account responses omit
the personal member. Requests without include=computed retain the original
object response shape and cache behavior.
The computed member is response-only. Supplying it in object create or update
JSON is a 400 Bad Request, including when its value is null.
The class object-list endpoint supports filtering and ordering by enabled computed fields:
GET /api/v1/classes/{class_id}/?computed.shared.display_name__icontains=server
GET /api/v1/classes/{class_id}/?computed.personal.my_priority__gte=10
GET /api/v1/classes/{class_id}/?sort=computed.shared.display_name
GET /api/v1/classes/{class_id}/?sort=computed.personal.my_priority.desc
shared and personal match the response namespaces. public and private
are accepted as aliases. Filters use the normal field__operator=value syntax,
combine with other filters using AND, and support not_ operator prefixes.
Requests may contain at most two computed filter parameters. Available
operators depend on the definition's declared result type:
| Result type | Operators |
|---|---|
string |
equals, iequals, contains, icontains, startswith, istartswith, endswith, iendswith, like, regex, in, is_null |
number, integer |
equals, in, gt, gte, lt, lte, between, is_null |
boolean |
equals, is_null |
object |
equals, contains, has_key, is_null |
array |
equals, contains, has_key, array_length, is_null |
Object and array filter values for equals and contains are JSON values of
the definition's declared type. contains uses JSON containment. has_key
tests an object key or string array element, and array_length accepts a
non-negative integer. is_null=true matches missing, null, failed, or
type-mismatched computed results; is_null=false matches successful non-null
results.
Computed sorts support the normal ascending,
descending, multi-field, deterministic tie-breaker, and cursor-pagination
behavior, with at most two explicit sort fields in any request that uses a
computed sort. Null or failed results sort first in ascending order and last
in descending order. Object and array results use PostgreSQL JSONB ordering.
The encoded cursor is limited to 64 KiB, so sorting large results can return
400 Bad Request when another page would require an oversized cursor; use
fewer sort fields or definitions with smaller outputs. JSON cursor values are
limited to 64 nested array or object levels; deeper values return
400 Bad Request.
A personal filter or sort is available only to the owning human user and still
requires class access. Service accounts cannot query personal definitions. The
include=computed parameter controls the response shape, not computed-query
availability; filtering or sorting without it still returns the raw object
shape. Responses whose result set or order depends on computed state use
Cache-Control: private, no-store.
With the default SQL authorization backend, computed filtering and sorting
happen in PostgreSQL before pagination. Current shared materialization is used
directly, with live evaluation only for missing or stale cache rows. Personal
querying evaluates the owner's entire enabled scope from raw object data
without write-time user fan-out, so scope work and output limits remain
identical to response enrichment. A policy backend first authorizes the real
objects in the class, then supplies that normalized ID set to PostgreSQL as a
bound array for computed counting, filtering, ordering, cursor comparison, and
page limiting. Live evaluation cost grows with the authorized candidate count,
object JSON size, and enabled-scope complexity; materialized shared fields are
preferable for high-volume queries. Computed-query database query counts do not
grow with page size. A raw nonterminal sorted page enriches only its
cursor-boundary object, while a terminal raw page skips enrichment; requests
without computed filtering or sorting retain the existing query path.
Computed-query reads use live values for missing or stale materializations but
defer cache repair to the rebuild path, keeping list requests read-only.
Declarative indexing is not yet supported. Unsupported computed operators and
mismatched filter value types fail with 400 Bad Request.
Computed fields can filter source objects and provide dimensions or numeric measures for the separate object-aggregate resource:
GET /api/v1/classes/{class_id}/object-aggregates?group_by=computed.shared.lifecycle
GET /api/v1/classes/{class_id}/object-aggregates?group_by=computed.personal.priority
GET /api/v1/classes/{class_id}/object-aggregates?computed.shared.lifecycle__equals=active&group_by=description
GET /api/v1/classes/{class_id}/object-aggregates?aggregate=average:computed.shared.health_score
Computed aggregation snapshots current definitions, then applies computed
filters, dimensions, and measures to immutable object snapshots; it does not
reload source objects or perform read repair. Measures accept number and
integer definitions and support sum, average, min, and max. Filters
use the same typed operators, aliases, and two-filter limit as class object
lists. A combined filter, grouping, and measure request shares one definition
snapshot. With a non-pushdown permission backend, ordinary-filter candidates
are authorized first; if none is visible, the endpoint returns an empty page
without resolving computed fields. SQL-pushdown computed filters resolve before
their database query, matching class object-list behavior even when the
filtered result is empty. Personal filtering, grouping, and measures are
limited to the requesting human owner's enabled definitions and still require
ReadClass; service accounts are rejected. Evaluation failures use one documented
unavailable bucket. Computed grouping responses use
Cache-Control: private, no-store. See querying.md
for selectors, response states, sorting, and cursor semantics.
Materialization freshness¶
Shared values are written in the same database transaction as each canonical object insert or update. A class advisory-lock protocol makes an object write observe either the complete old definition revision or the complete new one. A backfill locks and re-reads every object before writing its cache row, so it cannot restore older source data over a concurrent object update.
A stored shared materialization is stale when any of these conditions holds:
- it is missing, including for an object that predates the definition;
- its class evaluation revision differs from the current revision;
- its recorded class differs from the object's current class;
- its SHA-256 digest differs from the canonical, recursively key-sorted object
datadigest; - its value keys differ from the enabled shared definitions, a value does not match its definition's declared result type, or its stored error map is invalid.
This can happen during a definition rebuild, after an interrupted or failed rebuild, while restoring a backup, or after data was changed through an older writer that did not maintain the cache.
Stale storage never makes an enriched response stale. Hubuum evaluates the
current definitions against the returned raw data immediately and sets
materialization_stale to true. Ordinary include=computed reads attempt a
guarded read repair, so the next read normally uses the repaired row; repair
failure is recorded in metrics but does not replace the correct live value with
stale data. Computed filter and sort list reads remain read-only and defer
repair to the rebuild path. A manual rebuild is available for failed or
deliberately refreshed classes.
Backfills and read repair acquire definition and class locks before locking bounded object batches. The adapter enforces this order with transaction-bound capabilities; see Computed-field lock protocol for the covered operations, runtime checks, and database limitations.
Backup, events, and metrics¶
Backup version 5 includes computed-field definitions and their resource revisions as authoritative state. Class rebuild state and object materializations are excluded as rebuildable caches. Restore validates all definitions, increments each affected class revision, and queues rebuild tasks.
Shared definition create, update, and delete operations emit first-class computed-definition events with class and collection context. Rebuilds do not emit object update events. Personal definitions and values are excluded from the shared event stream and metric labels.
The runtime exports these low-cardinality metric families:
hubuum_computed_field_evaluations_totalhubuum_computed_field_errors_totalhubuum_computed_field_live_fallbacks_totalhubuum_computed_field_read_repairs_totalhubuum_computed_field_rebuild_batches_totalhubuum_computed_field_rebuild_completions_totalhubuum_computed_field_rebuild_duration_seconds