Error Codes
Every failure surfaced by Aperture is an APERTURE_* coded error. Each code carries a canonical message and, where operator action is meaningful, one or more fixup hints. This page is generated from the error Registry in errors/codes.go.
| Code | Message | Fixups |
|---|---|---|
APERTURE_ACTION_UNDECLARED | action is not declared on the object type | Add the action verb to the object type's declared action set, or grant a verb the type already declares. List the object type's actions to see the validated verb set. |
APERTURE_ATTRIBUTE_PROVIDER_FETCH | attribute provider returned an error | Inspect the wrapped cause for the underlying provider failure. Return APERTURE_NOT_FOUND from the provider for a key it does not know, so an unknown subject stays distinguishable from an unreachable directory. |
APERTURE_ATTRIBUTE_PROVIDER_INVALID | attribute provider registration or attribute key is invalid | Register a non-nil provider, and at most one per slot; a duplicate is refused rather than replaced so one directory cannot silently shadow another. Declare each attribute key at most once within a provider. Fetch with a real key: a principal id for the user and machine slots, an account id for the account slot. An empty key names nobody. Resolve the account wildcard "*" to a concrete account before fetching attributes; it is never a legal attribute key. |
APERTURE_ATTRIBUTE_PROVIDER_UNREGISTERED | no attribute provider is registered for the slot | Register an AttributeProvider for the slot before fetching its attributes. Check that the principal's kind maps to the slot you wired: a machine principal reads the machine slot, not the user slot. Deployment genuinely has no subjects of this kind? Then nothing should be fetching that slot — fix the caller rather than registering an empty provider. Seeing this from a decision? You should not: a Check/Enumerate/Explain against an unwired slot evaluates the floor bag and decides. This code reaches you only from a DIRECT attribute read (Fetch or Enumerate on the registry), so the caller to fix is that reader, not the decision path. |
APERTURE_ATTRIBUTE_SLOT_UNKNOWN | not an attribute slot | Use one of the three declared slots: user, machine, or account (provider.AttributeSlotUser / AttributeSlotMachine / AttributeSlotAccount). Converting a principal kind that arrived as a bare string? Cross over with provider.ParseAttributeSlot so an unknown kind fails at the conversion instead of resolving to an empty attribute bag. The slot set is closed on purpose — it names the parties a decision has. Model a further distinction as a FIELD in the bag, not as a fourth slot. |
APERTURE_AUTHZ_DENIED | the actor lacks the admin authority tier required for this mutation | Schema mutations (permission types, roles, object-types, providers, templates, rules) require system-admin authority: an allow grant on the admin action whose object covers system:. Grant and delegation mutations require account-admin authority in the TARGET account: an allow grant on the admin action whose object covers account:<acct>/admin:. Account-admin authority is confined to its own account; obtain authority in the account the mutation targets, or hold a broader (e.g. **) grant. |
APERTURE_BOOT | aperture failed to start | Check the APERTURE_* environment variables and any --config file. Confirm the storage backend (memory or sqlite) is reachable. |
APERTURE_CONFIG_INVALID | configuration is invalid | Validate the YAML config and APERTURE_* env vars against the docs. Enumerating an attribute slot that was declared without get_all: that slot is fetch-only by design, so add a get_all statement selecting a bare id, or read the slot through a fetch alone. |
APERTURE_DELEGATION_DENIED | the delegator may not bestow this grant | Bestow only grants that are a subset of your own effective allow grants in the account (same action and scope strategy, an equal-or-more-specific object pattern). Confirm you hold a 'may delegate' right whose object pattern covers the grant's object. Bestow grants only within an account you are a member of; cross-account bestowal is rejected. |
APERTURE_DELEGATION_NOT_DELEGATABLE | the permission is not flagged delegatable | Set Delegatable on the permission definition to allow it to be bestowed. |
APERTURE_ENTITY_UNMANAGED | this deployment does not manage the entity kind the write targeted | Set the switch for the kind named in the message — APERTURE_MANAGE_ACCOUNTS, APERTURE_MANAGE_PRINCIPALS, or APERTURE_MANAGE_MEMBERSHIPS — to true (the default), then RESTART aperture: the switches are read once at startup and never re-read. The three switches are independent; turning one on does not affect the others, so enable only the kind you meant to hand back to Aperture. Leaving a kind unmanaged is usually deliberate — those records are mastered by an upstream system. Make the change there and let it flow in, rather than flipping the switch. This is not a permission problem: no grant, role, or admin tier lifts it, and it refuses a system-admin exactly as it refuses anyone else. |
APERTURE_IDENTITY_INVALID | object identity is malformed | Use type:id segments joined by '/', e.g. account:acme/project:atlas/document:42. Ensure no segment is empty and every segment carries a ':' with a non-empty type and id. Remove illegal characters; types and ids allow letters, digits, and -._~@+ only ('*' marks a wildcard in patterns). |
APERTURE_IMPERSONATION_DENIED | the operator may not impersonate this target | Impersonate only within an account both the operator and the target are members of; cross-account impersonation is refused. Confirm the operator holds an impersonation right (augment or become) whose object pattern covers the target's identity. Become mode requires the stronger become right; an augment right alone cannot become a target. |
APERTURE_IMPERSONATION_EXPIRED | the impersonation session has expired | Start a fresh impersonation session; sessions are time-boxed and expire automatically. |
APERTURE_INVALID_INPUT | input failed validation | Re-check the request shape against the command or API contract. |
APERTURE_INVALID_TOKEN | the presented bearer credential failed verification | Confirm the token is a well-formed JWT signed by the configured issuer's keys. Check the token issuer and audience match APERTURE_OIDC_ISSUER and APERTURE_OIDC_AUDIENCE, and that it has not expired. For a parsec adapter, confirm the token was minted by the broker sharing the configured keyring/secret. |
APERTURE_METADATA_INVALID | object metadata violates the metadata value model | Make each field a scalar, a []any of scalars, or a map[string]any whose values are scalars, scalar arrays, or one further object level. Replace an array of objects with a scalar array (e.g. a list of ids) — arrays of objects are rejected at any position. Flatten a value that nests past the depth cap, or raise provider.ValueLimits.MaxDepth for the loader. Shorten a value over the per-value size cap, or raise provider.ValueLimits.MaxBytes for the loader. |
APERTURE_NOT_FOUND | the referenced entity was not found | Confirm the identifier exists in the current account scope. |
APERTURE_PROVIDER_FETCH | object provider returned an error | Inspect the wrapped cause for the underlying provider failure. Return APERTURE_NOT_FOUND from the provider for an object that does not exist. |
APERTURE_PROVIDER_INVALID | object provider registration is invalid | Register a non-nil provider under a non-empty object-type key. Register each object type at most once; check for a duplicate registration. |
APERTURE_PROVIDER_REFERENCE_INVALID | a declared object reference is not usable | Register a provider for the target object-type: a references: entry may only point at a type this registry serves. Declare each field at most once per object-type; several fields may point at the same target, but one field has one target. Declare the reference on the HOLDING side — the type whose provider actually returns the field — because that is the only side with a value to resolve. Resolving a field means declaring it first: reg.DeclareReference("dataset", "current_brands", "brand"), or a references: entry in the provider's seed block. |
APERTURE_PROVIDER_REFERENCE_MISMATCH | a reference field's value does not identify an object of its declared target type | Store FULL canonical identities in a reference field ('brand:1', 'account:acme/brand:1'), not bare primary keys — compose them where the data is loaded, e.g. SELECT 'brand:' || b.id. Make the field a string or a list of strings; a number, a map, or a list holding anything but strings cannot be an identity. Check the declared target against the values the field actually carries: an identity whose terminal segment type is not the declared type is rejected rather than skipped. |
APERTURE_PROVIDER_UNREGISTERED | no object provider is registered for the object type | Register an ObjectProvider for the object type before fetching its metadata. Confirm the object identity's terminal segment type matches a registered provider key. Filtering an enumeration by metadata fields? Build the engine with engine.WithMetadata(registry) — the same provider registry the scope lister uses — or drop the field predicates. |
APERTURE_RULE_EVAL | rule evaluation failed | Inspect the wrapped cause for the underlying evaluation failure. Ensure the rule expression yields a boolean for the supplied context. |
APERTURE_RULE_INVALID | rule AST is malformed | Give each logical node the right child count: and/or take two or more, not takes exactly one. Give every comparison a left and right operand, and every literal a scalar value. Write variable references as dotted identifier paths, e.g. object.classification. |
APERTURE_RULE_NOT_FOUND | the referenced rule was not found | Confirm the rule reference exists in the configured rule source. |
APERTURE_RULE_TYPE_ERROR | rule failed expression type checking | Compare compatible types and make the rule evaluate to a boolean. Call only functions registered with the rules engine. |
APERTURE_RULE_UNKNOWN_VARIABLE | rule references an unknown variable | Reference variables under a known context root: object, principal, account, or action. Check for a typo in the variable's root segment. |
APERTURE_SCOPE_INVALID | scope strategy reference is malformed | Use 'strategy' or 'strategy;param=value' form, e.g. inclusive;ids=account:acme/document:42. Give an inclusive/exclusive strategy an 'ids' list or a 'rule' reference; implicit takes no configuration. |
APERTURE_SCOPE_LISTER_UNCONFIGURED | scope enumeration requires an object lister that is not configured | — not applicable |
APERTURE_SCOPE_RULE_UNCONFIGURED | scope rule path requires a rule evaluator that is not configured | — not applicable |
APERTURE_SCOPE_UNKNOWN_STRATEGY | scope strategy is not registered | Use a built-in strategy (literal, implicit, inclusive, exclusive) or register the custom key with the scope registry. |
APERTURE_SQL_PROVIDER_AMBIGUOUS | SQL provider's fetch statement returned more than one row for one key | Filter the fetch statement on a unique or primary key so one identity — or one subject id — selects at most one row. A join that fans out is the usual cause; aggregate or de-duplicate the fanned-out side instead of adding LIMIT 1, which would make the metadata depend on an unspecified row order. |
APERTURE_SQL_PROVIDER_CONNECTION | a declared database connection could not be resolved into a live pool | Set the environment variable named by the connection's dsn_env: to a non-empty DSN before starting the process. Check that every kind: sql provider entry's connection: matches a name declared in the top-level connections: block. Give pool settings valid values: conn_max_lifetime and query_timeout are Go durations ("30m", "5s"), and query_timeout must be positive — there is no 'no timeout' setting. Verify the DSN's host, port, database, and credentials by connecting with psql; the driver's message is redacted here because a DSN parse failure commonly echoes the password. |
APERTURE_SQL_PROVIDER_DSN_LITERAL | a declarative database connection carries a literal dsn instead of dsn_env | Replace the connection's dsn: key with dsn_env: naming the environment variable that holds the DSN. Export the DSN in the process environment (or a .env file the deployment loads) rather than writing it into the seed file. Rotate the credential if a literal DSN was ever committed — the seed file is a version-controlled artifact. |
APERTURE_SQL_PROVIDER_QUERY | SQL provider could not run its statement | Inspect the wrapped driver error for the underlying database failure. Check the statement's placeholder count: a fetch statement binds exactly one parameter — the identity's terminal segment value for an object provider, the bare subject id for an attribute provider. Use the placeholder syntax your engine speaks — Aperture passes placeholders through untouched and never rewrites $1 to ?. Confirm the database is reachable and the connection's role can read the table; raise Config.Timeout if the statement is legitimately slow. |
APERTURE_SQL_PROVIDER_ROW_IDENTITY | SQL provider could not turn a row's id column into a usable key | Object provider: compose the full identity in the get-all statement's id column — SELECT 'brand:' || b.id AS id — because a bare primary key is not an identity and Aperture supplies no template. Attribute provider: select the BARE subject id — SELECT u.id AS id — and never 'user:' || u.id, which is a legal opaque key that no principal id will ever match, so the slot enumerates and then answers nothing. Name the key column id, or set the provider's id column to the alias the statement actually uses. Make the id column textual and never NULL: cast a numeric or uuid key with ::text before concatenating it. Check that the identity's terminal segment type is the object-type this provider is registered under; a 'brand:1' row served by the 'dataset' provider is rejected rather than cached. |
APERTURE_SQL_PROVIDER_SCAN | SQL provider could not read a row into metadata | Give every selected expression a name, and alias duplicates: each result column becomes a metadata field keyed by its column name. Cast or serialise a column whose Go type the provider does not map (the driver value's type is named in the error, alongside the types that are mapped). A []byte column is decoded as JSON, never as a string: wrap an array in to_jsonb(...), and cast a numeric, uuid, or bytea to ::text, ::float8, or encode(...) in the statement. A list-valued field only arrives as a list when the statement casts it — SELECT to_jsonb(tags) AS tags, not SELECT tags, which yields the raw array literal as a string that silently matches nothing. |
APERTURE_STORAGE | the storage backend returned an error | Inspect the wrapped cause for the underlying storage failure. |
APERTURE_STORAGE_CONSTRAINT | the database refused the write because it would break referential integrity | Delete the rows that reference the record first: a principal's memberships and group memberships, a permission's role assignments and grants, a role's principal assignments, an object type's permissions, an account's memberships and grants, and the grants that name a principal, role, or group as their subject. Creating a record? Create what it references first — a permission needs its object type, a membership and a group member need their principal, a grant and a role assignment need their permission, and a membership and a grant need their account. Refused on a grant's subject? subject_kind chooses the table subject_id must exist in — principal, role, or group. A grant naming a role id under kind "group" is refused even though that id exists elsewhere. Refused on an account id? It must name a real account, or be exactly "" (the all-accounts wildcard). "" is a sentinel, not an account: it needs no account record and cannot be given one. Ids are immutable in Aperture: to change one, create the new record, move the children, then delete the old one — an UPDATE that moved a key is refused rather than silently re-parenting. Seeing this from Setup rather than a write? The connection is not enforcing foreign keys. Open the store with sqlite.Open, which forces _pragma=foreign_keys(1) into the DSN whatever the caller passed. |
APERTURE_STORAGE_SCHEMA_INCOMPATIBLE | the existing database was written by an incompatible build and cannot be upgraded | Recreate the database: move or delete the old file, start Aperture so Setup builds the schema fresh, then re-seed it. Point the store at a new, empty database if you need to keep the old file for reference. Do not expect an in-place upgrade: Aperture ships no migration tool and no schema versioning, so a schema break is a hard break by design. |
APERTURE_TEMPLATE_INVALID | the provisioning template is malformed | Give the template a non-empty name, a version of at least 1, and at least one grant. Declare every parameter a grant references; write references as ${name} with a declared parameter. Give each template grant a valid subject, a permission id, an allow/deny effect, and a non-empty object pattern. |
APERTURE_TEMPLATE_PARAM | the template apply supplied invalid parameters | Supply a value for every parameter the template declares, and no parameters it does not. A segment-typed parameter value must be a legal identity component: letters, digits, and -._~@+ only. |
APERTURE_UNAUTHENTICATED | the request could not be resolved to a known principal | Present a credential: send an Authorization: Bearer <token> header. With the dev/static authenticator the bearer IS the principal id; send a non-empty value. Confirm the verified token carries the configured principal claim (APERTURE_AUTH_PRINCIPAL_CLAIM, default 'sub'). |
APERTURE_UNIMPLEMENTED | this surface is not yet implemented | — not applicable |