Projections
Errors from the projections: block — shard keys, lookups, resolvers, partitioning, and the runtime contract projections must satisfy.
42 error codes in this section.
Projection Shard
VERR_PROJECTION_SHARD_INVALID_LOCATION
shard() used in invalid location. shard() is used inside an aggregate expression, query, or any context outside derive:.
Impact: DDL or query plan generation fails; projection cannot deploy.
aggregates:
CheckedIn:
checkin_count:
op: add
by: "shard(4, event.payload.venue_id)" # invalid herederive:
venue_shard: "shard(4, event.payload.venue_id)"
aggregates:
CheckedIn:
checkin_count:
op: add
by: "derive:venue_shard"VERR_PROJECTION_SHARD_INVALID_COUNT
shard() count is invalid. The shard count argument is zero, negative, or exceeds the plan’s maximum.
Impact: Projection cannot be compiled or deployed.
derive:
venue_shard: "shard(0, event.payload.venue_id)" # 0 is invalidderive:
venue_shard: "shard(8, event.payload.venue_id)" # 1–32VERR_PROJECTION_SHARD_NON_DETERMINISTIC
shard() expression is non-deterministic. The shard expression argument uses now() or random() instead of a stable event-scoped value.
Impact: Same event shards to a different bucket on replay; projection rows are scattered incorrectly.
derive:
shard: "shard(8, now())" # non-deterministicderive:
shard: "shard(8, event.entity_id)" # stableProjection Lookup Safety
VERR_PROJECTION_LOOKUP_AGGREGATION_VIOLATION
lookup() inside aggregate expression. A lookup() call is used inside an aggregate by: expression.
Impact: Projection compilation fails.
aggregates:
CheckedIn:
venue_total:
op: add
by: "lookup(venue, event.payload.venue_id).weight"derive:
venue_weight: "lookup(venue, event.payload.venue_id).weight"
aggregates:
CheckedIn:
venue_total:
op: add
by: "derive:venue_weight"VERR_PROJECTION_LOOKUP_BUCKET_VIOLATION
lookup() in bucket expression. A lookup() call is used inside a bucket.field expression.
Impact: Projection compilation fails.
bucket:
field: "lookup(venue, id).opened_at" # invalid# Bucket fields must be event-scoped.
bucket:
field: event.tsVERR_PROJECTION_LOOKUP_DYNAMIC_VIOLATION
lookup() with dynamic identity expression. The lookup ID argument uses a computed or aggregated value rather than a raw event-scope path.
Impact: Projection may load the wrong entity for some events.
derive:
name: "lookup(venue, shard_id + offset).name" # computed IDderive:
name: "lookup(venue, event.payload.venue_id).name" # raw event pathVERR_PROJECTION_LOOKUP_NESTED_VIOLATION
Nested lookup() call. A lookup() call is nested inside another lookup() call.
Impact: Projection compilation fails.
derive:
name: "lookup(venue, lookup(event, id).venue_id).name"# Use a resolver block to chain lookups:
resolver:
event_entity:
entity: event
key: event.entity_idVERR_PROJECTION_LOOKUP_LIMIT_EXCEEDED
Too many lookup() calls. The projection exceeds the plan-permitted maximum number of lookup() calls.
Impact: Projection is rejected by the tier validator.
# 5 lookup() calls on a plan that allows max 3# Reduce lookup count, or upgrade to a higher plan tier.VERR_PROJECTION_LOOKUP_FANOUT_VIOLATION
lookup() resolves to multiple entities. A lookup() call uses a relationship/collection field as the identity argument, which could resolve to multiple entities.
Impact: Projection produces wrong or duplicated rows.
derive:
name: "lookup(venue, event.payload.venue_ids).name" # plural — fan-out# lookup() must resolve to exactly one entity.
# Use the resolver block for relationship traversal.VERR_PROJECTION_COMPLEXITY_EXCEEDED
Projection exceeds complexity threshold. The combination of derive fields, aggregates, lookups, and mutations pushes the projection past the complexity limit.
Impact: Projection is rejected; no DDL is generated.
# Very wide projection with 20 derive fields, 5 lookups, and complex mutations# Split the projection into two or more narrower projections.VERR_PROJECTION_COST_TOO_HIGH
Projection estimated CU cost too high. The projection’s estimated compute-unit cost exceeds the plan maximum.
Impact: Projection is rejected by the tier validator.
# Very wide projection with high shard count and many join lookups# Reduce lookup count, shard count, or derive complexity.
# Consider splitting into multiple projections.VERR_PROJECTION_TIER_VIOLATION
Projection exceeds plan tier limits. The projection exceeds a plan-tier limit: lookup count, shard count, bucket interval, or complexity score.
Impact: Projection cannot be deployed on the current plan.
# 3 lookups on Starter tier that allows max 1# Reduce usage to match your plan tier, or upgrade.Projection Resolver
VERR_PROJECTION_RESOLVER_ENTITY_NOT_FOUND
Resolver entity does not exist. A resolver’s entity: field references a state name that is not defined in the spec.
Impact: Resolver loads null for every event; all derive fields using this resolver produce null.
resolver:
venue:
entity: venue # 'venue' not in state:
key: event.payload.venue_idstate:
venue: { ... }
# then add the resolverVERR_PROJECTION_RESOLVER_KEY_NOT_FOUND
Resolver key field missing. A resolver definition is missing the required key: field.
Impact: Resolver cannot identify which entity to load; loads null for every event.
resolver:
venue:
entity: venue # key is missingresolver:
venue:
entity: venue
key: event.payload.venue_idVERR_PROJECTION_RESOLVER_KEY_INVALID
Resolver key is not event-scoped. A resolver key: is not a valid event-scoped expression (must start with event.payload.* or event.*). An invalid key silently resolves to null for every event, loading the wrong entity.
Impact: Resolver loads the wrong entity for every event; all related derive fields are incorrect.
resolver:
venue:
entity: venue
key: venue_id # not event-scopedresolver:
venue:
entity: venue
key: event.payload.venue_idVERR_PROJECTION_RESOLVER_DUPLICATE_ALIAS
Duplicate resolver alias. The same alias name is defined twice in the resolver block of a single projection.
Impact: Second definition silently overrides the first; resolver behaviour is unpredictable.
resolver:
venue: { entity: venue, key: event.payload.venue_id }
venue: { entity: place, key: event.payload.place_id } # duplicate# Use unique alias names: 'venue' and 'place'VERR_PROJECTION_RESOLVER_RESERVED_ALIAS
Resolver alias uses reserved name. A resolver alias shadows the reserved names event or state.
Impact: All derive expressions using event.* or state.* may resolve against the resolver instead.
resolver:
event: # reserved name
entity: venue
key: event.payload.venue_idresolver:
venue:
entity: venue
key: event.payload.venue_idVERR_PROJECTION_RESOLVER_UNDEFINED_ALIAS
Derive references undefined resolver alias. A derive expression references a resolver alias that is not declared in the resolver block.
Impact: Derive field resolves to null for every event.
# No 'venue' alias in resolver:
derive:
venue_name: "venue.name" # undefined aliasresolver:
venue:
entity: venue
key: event.payload.venue_id
derive:
venue_name: "venue.name"VERR_PROJECTION_RESOLVER_INVALID_PATH
Resolver alias path does not exist on target state. A derive expression uses a resolver alias path that does not exist on the target entity’s state schema.
Impact: Derive field produces null for every event; no SQL error but data is silently wrong.
# venue state has no 'postal_code' field
derive:
venue_zip: "venue.postal_code" # field doesn't exist# Use a field that exists in the venue state schema:
derive:
venue_zip: "venue.address.zip"VERR_PROJECTION_RESOLVER_CIRCULAR
Resolver key references another resolver alias. A resolver’s key: references another resolver alias instead of an event-scope path, creating a chain or cycle.
Impact: Resolver cannot evaluate; projection fails for every event.
resolver:
venue:
entity: venue
key: place.id # references 'place' alias
place:
entity: place
key: event.payload.place_idresolver:
venue:
entity: venue
key: event.payload.venue_id # direct event-scope reference
place:
entity: place
key: event.payload.place_idProjection Partitioning
VERR_PROJECTION_PARTITION_INVALID_STRATEGY
Invalid partition strategy. partition.strategy is missing or not one of: range, hash, list.
Impact: Projection DDL cannot be generated; deployment fails.
partition:
strategy: sharded # invalidpartition:
strategy: range # range | hash | listVERR_PROJECTION_PARTITION_FIELD_MISSING
Partition field missing or undeclared. partition.field is missing or references a column that is not declared on the projection.
Impact: Deployment fails; table cannot be created.
partition:
strategy: range
field: created_at # not in projection fields# Add 'created_at' to fields: or derive: first,
# then reference it in partition.field.VERR_PROJECTION_PARTITION_FIELD_NOT_IN_PK
Partition field not in primary key. Postgres requires all partition key columns to be part of the primary key.
Impact: DDL creation fails with a Postgres error at deploy time.
target:
primary_key: [checkin_id]
partition:
field: created_at # not in primary_keytarget:
primary_key: [checkin_id, created_at]
partition:
field: created_atVERR_PROJECTION_PARTITION_INVALID_FIELD_TYPE
Partition field type incompatible with strategy. The partition field type is not supported by the chosen strategy (e.g. GEOGRAPHY_POINT on range).
Impact: Deployment fails with a Postgres DDL error.
fields:
location: GEOGRAPHY_POINT
partition:
strategy: range
field: location # geo type incompatible with range# Use TIMESTAMP/TIMESTAMPTZ for range, UUID/TEXT/INT for hash or list.VERR_PROJECTION_PARTITION_RANGE_INTERVAL_REQUIRED
Range partition missing interval. A range partition on a temporal field is missing the required interval configuration.
Impact: Deployment fails; Postgres cannot create child partitions.
partition:
strategy: range
field: created_at
# interval is missingpartition:
strategy: range
field: created_at
interval: "1 month"VERR_PROJECTION_PARTITION_HASH_COUNT_REQUIRED
Hash partition missing partitions count. A hash partition is missing the required partitions count.
Impact: Deployment fails; Postgres cannot create hash partition buckets.
partition:
strategy: hash
field: user_id
# partitions count is missingpartition:
strategy: hash
field: user_id
partitions: 8VERR_PROJECTION_PARTITION_RANGE_INTERVAL_INVALID
Range partition interval has invalid format. The range partition interval value does not match the recognised format: N day|week|month|year.
Impact: Deployment fails; Postgres rejects the interval string.
partition:
interval: "monthly" # invalid formatpartition:
interval: "1 month" # N day|week|month|yearVERR_PROJECTION_PARTITION_RANGE_PRECREATE_NEGATIVE
Range partition precreate is negative. The partition.precreate value is a negative integer.
Impact: Compilation fails; no partitions are created.
partition:
precreate: -1partition:
precreate: 3 # create 3 future partitionsVERR_PROJECTION_PARTITION_HASH_COUNT_TOO_HIGH
Hash partition count exceeds maximum. partition.partitions exceeds the plan-permitted maximum bucket count.
Impact: Projection is rejected by the tier validator.
partition:
partitions: 512 # exceeds plan maximum# Reduce partition count to within the plan maximum (typically 32 or 64).VERR_PROJECTION_PARTITION_STRAY_FIELD
Partition config has field for wrong strategy. A partition config field belongs to a different strategy (e.g. interval on a hash partition).
Impact: Compilation fails with a stray-field error.
partition:
strategy: hash
partitions: 8
interval: "1 month" # interval is only for rangepartition:
strategy: hash
field: user_id
partitions: 8VERR_PROJECTION_PARTITION_KEY_UPDATE_CONFLICT
bulk_update sets partition key column. A bulk_update mutation includes the partition key column in its set: block. Postgres forbids updating partition keys on partitioned tables.
Impact: bulk_update throws a Postgres error at runtime for every matching event.
mutations:
UserUpdated:
op: bulk_update
set:
user_id: event.payload.user_id # user_id is the partition field# Remove the partition key from set:.
# To change partition key, delete and re-insert the row.VERR_PROJECTION_PARTITION_TABLE_CONFLICT
Multiple projections have conflicting partition configs for same table. Two projections targeting the same table declare different partition configurations.
Impact: Deployment fails; Postgres cannot apply conflicting partition DDL.
# projection_a: strategy: range on created_at
# projection_b: strategy: hash on user_id (same table)# All projections targeting the same table must declare identical partition configs.Projection Runtime-Contract
VERR_PROJECTION_DUPLICATE_TABLE_PK_CONFLICT
Conflicting primary keys on shared table (legacy code). Two projections target the same physical table but declare different primary_key lists. See VERR_PROJECTION_TABLE_PRIMARY_KEY_CONFLICT for the canonical code.
Impact: DDL for the second projection collides with the first’s PK constraint at deploy time.
# projections A and B share a table but have different primary_key lists# All projections targeting the same table must declare identical primary keys.VERR_PROJECTION_TABLE_PRIMARY_KEY_CONFLICT
Conflicting primary keys on shared table. Two projections target the same physical table but declare conflicting primary_key lists. DDL collision — deploy will fail or silently truncate existing rows.
Impact: Deployment fails with a constraint violation, or one projection’s PK is silently ignored.
projections:
daily_checkins:
target:
table: checkin_totals
primary_key: [venue_id, date]
venue_totals:
target:
table: checkin_totals
primary_key: [venue_id] # conflict!# All projections sharing 'checkin_totals' must agree on the same primary_key.VERR_PROJECTION_AGGREGATE_FIELD_MISSING_TYPE
Aggregate target field has no declared type. An aggregate target field used by add/sub/set has no entry in fields:. The DDL generator defaults to BIGINT, silently breaking set operations that write non-integer values.
Impact: DOUBLE/TEXT values stored as aggregates are silently truncated or fail at runtime.
# No fields.average_score declaration
aggregates:
RatingSubmitted:
average_score:
op: set
by: event.payload.scorefields:
average_score: DOUBLE # declare the type
aggregates:
RatingSubmitted:
average_score:
op: set
by: event.payload.scoreVERR_PROJECTION_LOOKUP_FIELD_PATH_MISSING_STATE
lookup() path missing in state schema (legacy code). A lookup(stream, expr).fieldPath references a path that does not exist on the resolved stream’s state schema. See VERR_PROJECTION_LOOKUP_FIELD_NOT_FOUND for the canonical code.
Impact: Derive field silently produces null for every event.
derive:
zip: "lookup(venue, event.payload.venue_id).postal_code"
# venue has no postal_codederive:
zip: "lookup(venue, event.payload.venue_id).address.zip"VERR_PROJECTION_LOOKUP_FIELD_NOT_FOUND
lookup() or resolver path not found in target state. A lookup(stream, expr).fieldPath expression or resolver alias path references a field that does not exist on the target state schema. The worker silently produces null for every event.
Impact: Projection columns are all null; no SQL error but data is silently wrong.
derive:
venue_zip: "lookup(venue, event.payload.venue_id).postal_code"
# 'postal_code' not in venue statederive:
venue_zip: "lookup(venue, event.payload.venue_id).address.zip" # correct pathVERR_PROJECTION_AGGREGATE_DERIVE_FIELD_MISSING
Aggregate by: derive:x references undeclared derive field. An aggregate by: derive:x references a derive field x that is not declared in the projection’s derive: block. The worker defaults by to 1, silently corrupting counter increments.
Impact: All increments use 1 instead of the intended delta value; counters are incorrect.
# score_delta NOT in derive:
aggregates:
RatingSubmitted:
rating_total:
op: add
by: "derive:score_delta" # missing derive fieldderive:
score_delta: "event.payload.score_delta"
aggregates:
RatingSubmitted:
rating_total:
op: add
by: "derive:score_delta"VERR_PROJECTION_AGGREGATE_EVENT_FIELD_INVALID
Aggregate by: event path is malformed. An aggregate by: event.payload.foo or by: event.foo is not a well-formed event-scope expression. The worker silently substitutes 0, producing incorrect counter values.
Impact: All aggregate increments use 0; counters are silently zeroed.
aggregates:
RatingSubmitted:
rating_total:
op: add
by: "payload.score" # missing 'event.' prefixaggregates:
RatingSubmitted:
rating_total:
op: add
by: "event.payload.score"VERR_PROJECTION_INDEX_COLUMN_MISSING
Index column not found on projection. An explicit indexes: entry references a column not declared in fields:, derive:, primary_key, or bucket_start. The DDL generator silently skips or errors on the index.
Impact: Index is not created; queries that rely on it perform full table scans.
fields:
venue_id: TEXT
indexes:
- columns: [venue_id, created_at] # created_at undeclaredfields:
venue_id: TEXT
created_at: TIMESTAMPTZ # add the field
indexes:
- columns: [venue_id, created_at]VERR_PROJECTION_INDEX_TYPE_MISMATCH
Index type incompatible with field type. An explicit index type is incompatible with the declared field type. GIST requires GEOGRAPHY_POINT/GEOGRAPHY; GIN requires TEXT/STRING.
Impact: Deployment fails with a Postgres DDL error.
fields:
venue_id: TEXT
indexes:
- columns: [venue_id]
type: GIST # GIST only valid on GEOGRAPHY_POINTfields:
location: GEOGRAPHY_POINT
indexes:
- columns: [location]
type: GISTVERR_PROJECTION_FIELD_TYPE_UNKNOWN
Unknown field type. A projection declares a field with a type string the compiler does not recognise. The DDL generator silently falls back to TEXT, hiding schema intent and causing type-mismatch bugs.
Impact: Aggregates on numeric fields may fail or produce wrong results due to TEXT default.
fields:
score: float64 # not recognised
created_at: datetime # not recognisedfields:
score: DOUBLE
created_at: TIMESTAMPTZ
# Supported: TEXT STRING VARCHAR INT INTEGER BIGINT SMALLINT
# DOUBLE FLOAT NUMERIC DECIMAL BOOLEAN BOOL
# TIMESTAMP TIMESTAMPTZ DATE UUID
# GEOGRAPHY_POINT GEOGRAPHY JSONB JSON