Access Modifiers
CK language 2 adds three modifiers. All of them require ckLanguage: 2 (message 90 otherwise).
| Modifier | Applies to | Values | Default in a v2 model | Purpose |
|---|---|---|---|---|
visibility | type, record, enum, attribute, association role, interface, method | Public, Internal | Public | May other models reference the element? |
derivable | type, record | Model, Any | Model | May other models derive from the element? |
access | attribute assignment on a type, record or association role | ReadWrite, ReadOnly, MethodOnly, Hidden | ReadWrite | How may the generic API read and write the attribute? |
In a CK language 1 model every element is effectively Public, derivable: Any and access: ReadWrite. The existing modifiers isAbstract and isFinal keep their meaning in both languages.
visibility
Internal elements can only be referenced inside their own model. Any reference from another model is refused with message 112 — deriving from an internal type or record, assigning an internal attribute (on a type, record, association role or interface member), using an internal record or enum as a value type, implementing or extending an internal interface, using an internal association role or target, and method parameters/results that use internal records or enums.
attributes:
- id: InternalNote
valueType: String
visibility: Internal
Error 112 types/types.yaml: 'Acme.Plant-1.0.0/Note-1' references the internal attribute 'Acme.Assets-1.0.0/InternalNote-1'
of another model. Internal elements (visibility: Internal) can only be referenced inside their own model.
Inside its own model an internal element may only be referenced by other internal elements. A public element that references an internal element of the same model is refused with message 129 ("inconsistent visibility"), because other models would reach the internal element through it:
- a public type that derives from an internal type, implements an internal interface, assigns an internal attribute, or uses an internal association role or target;
- a public attribute whose value record or enum is internal, a public record that derives from an internal record or assigns an internal attribute, a public association role that assigns an internal attribute;
- a public interface that extends an internal interface, has an internal member, association role or target, or declares an internal method;
- a public method of a public type whose parameter or result uses an internal record or enum;
- a type that redeclares a method of a public interface it implements as internal.
Error 129 types/types.yaml: Public 'Acme.Assets-1.0.0/Pump-1' references the internal type 'Acme.Assets-1.0.0/Helper-1'
(base type). A public element may only reference public elements of its own model (visibility consistency): make the
referenced element public or 'Acme.Assets-1.0.0/Pump-1' internal.
Allowed: internal → public, internal → internal, public → public, and internal methods on public types (they may use internal records and enums).
Internal elements are an implementation detail of their model: they are not part of the compatibility surface. Changing one is at most Minor (see SemVer); rule 129 is what makes that safe.
derivable
derivable controls which models may derive from a type or record:
Model— only the declaring model may derive from it;Any— every model may derive from it.
Omitted, it is Model in a ckLanguage: 2 model and Any in a v1 model. Declare derivable: Any on every type or record that is meant as a base for other models:
types:
- typeId: Device
isAbstract: true
derivable: Any # other models may derive from Device
derivedFromCkTypeId: ${System}/Entity
- typeId: Sensor # derivable defaults to Model
derivedFromCkTypeId: ${this}/Device
Deriving from Sensor in another model fails — also when that other model is a CK language 1 model:
Error 113 types/types.yaml: Type 'Acme.Plant-1.0.0/Pump-1' derives from 'Acme.Assets-1.0.0/Sensor-1' of another model,
which only its own model may derive from (derivable: Model). The base model must declare 'derivable: Any' to allow it.
derivable and isFinal are independent: isFinal: true forbids derivation everywhere, derivable: Model only outside the model. In generated C# a derivable: Model type without a subtype in its own model becomes sealed.
Changing derivable from Any to Model is a Major change, Model → Any a Minor one. Moving an existing model from CK language 1 to 2 flips the default of every type and record to Model — a Major change unless each of them declares derivable: Any. See Versioning rules.
Enforcement on import
visibility and derivable are not only compile-time checks. The engine repeats them whenever a model is resolved — on compile, on publish and on every import into a tenant. A forged model or one compiled by an older compiler cannot bypass them: it fails with 112/113 and is not imported.
The check applies to the model being compiled, published or imported — not to models that are already installed. If a base model later makes an element Internal or derivable: Model, its installed dependents are not re-checked and stay Available; they fail with 112/113 only when they are compiled or imported again. Such a change is a Major version change of the base model (see SemVer), so publish it only with a new major.
access
access is set on an attribute assignment — the entry under attributes: of a type, record or association role — not on the attribute definition:
types:
- typeId: Device
attributes:
- id: ${this}/SerialNumber
name: SerialNumber
access: ReadOnly
- id: ${this}/Firmware
name: Firmware
isOptional: true
access: MethodOnly
- id: ${this}/ApiKeyHash
name: ApiKeyHash
isOptional: true
access: Hidden
| Value | Meaning |
|---|---|
ReadWrite | Default. Readable and writable through the generic GraphQL API. |
ReadOnly | Intended to be set on create only and then read-only. |
MethodOnly | Readable, but not writable through the generic create/update mutations — it is meant to be changed by methods only. Not part of the generated GraphQL input types. |
Hidden | Never exposed through the GraphQL API — no field in output or input types, not filterable, not sortable. |
ReadOnly and MethodOnly in this phaseReadOnly is carried through compile, persistence and the CK meta API, but not enforced yet: the generic GraphQL mutations still accept it on update. Method invocation is not available yet, so a MethodOnly attribute currently cannot be changed through GraphQL at all — only by blueprint seed data, ImportRt or service code.
Compiler rules for Hidden
A Hidden value must not leak through any other model feature. The compiler refuses:
| Rule | Message |
|---|---|
A display rule (displayNameRule, displayDescriptionRule) or Text index path reaches a Hidden attribute; an ownerAttributePath reaches a Hidden or MethodOnly one (record segments included) | 105 |
Any other index (Unique, UniqueNotDeleted, Ascending, ...) reaches a Hidden attribute — a unique index reveals values through duplicate-key errors | 106 |
A Hidden assignment declares autoCompleteValues | 107 |
access: Hidden on an association role attribute (not supported; ReadOnly and MethodOnly are) | 108 |
| In a v2 model, an index path segment that names no attribute (paths are matched case-insensitively, like MongoDB does) | 109 |
| A type assigns an interface member as Hidden | 99 |
Index and rule paths are resolved case-insensitively, so passwordHash reaches PasswordHash. Because all types of a collection share the stored attribute names, rules 105 and 106 also fire when the index or rule is declared on one type and another type of the same collection (a sibling or derived type, possibly in another model) assigns that attribute as Hidden.
Import backstop for indexes. Message 106 is the primary protection, checked by the compiler. As a second line of defence for hand-edited or older compiled models, index maintenance on import into a tenant skips index paths that reach a Hidden attribute and logs an ERROR (Skipping Hidden attribute ...); an index left without fields is not created. Treat the backstop as a safety net, not as a replacement for a clean compile.
What Hidden protects
Hidden is enforced by the asset repository's GraphQL API:
- Read: Hidden attributes have no field on the entity type, the abstract-type and CK interface types, record types and the generic
attributesprojections of entities, records and associations. When an entity's CK type is unknown, every attribute name that is Hidden anywhere in the tenant is dropped (fail closed). - Write: the generic
create/updatemutations and query-row writes reject Hidden (andMethodOnly) attributes withATTRIBUTE_NOT_WRITABLE. - Filter, sort, search, aggregation, group-by: a Hidden path is rejected with
ATTRIBUTE_NOT_QUERYABLE. - Query columns and entity selectors: Hidden columns are not offered and cannot be requested; selector keys such as
members.someType[apiKeyHash='X']are rejected (they would be an equality oracle). - Association and navigation paths: filters and sorts passed to association/navigation connections are checked against the target type and every type derived from it; paths across navigations are matched by attribute name against every Hidden assignment in the tenant.
- Record paths: a Hidden record-valued attribute also protects every
record.fieldbelow it. - Archives: Hidden attributes may not be archived — see below.
Access errors carry the error code, the attribute path and the operation, but never a value — also not in the Development environment; values in entity-selector paths are redacted ([apiKeyHash=…]).
What Hidden does not protect (yet)
Hidden is an API-surface guarantee, not encryption. For entity data it is enforced only by the asset repository's GraphQL API (plus the compiler rules above); for stream data the engine enforces the archive rule. Other services and paths read and write entities through the engine without access checks. In particular it does not cover:
ImportRt— runtime-data imports can write Hidden (andMethodOnly) attributes;- runtime export — RT exports contain Hidden and
MethodOnlyvalues; - identity fields —
System.Identity/User(user tokens, two-factor and lockout fields, password hash) is still a CK language 1 model and therefore uses noaccessat all; - services using the engine directly — pipelines (mesh adapter), bots and other services are not restricted by
access; - computed columns of queries and association
targetCkAttributeIdsare not checked against Hidden attributes; - integrity of Hidden and
MethodOnlyvalues on generic writes — a generic update that writes a whole record rebuilds it from the input fields, so Hidden orMethodOnlysub-attributes of that record are cleared;clearSecretAttributescan clear aSecretattribute that is also Hidden orMethodOnly. Avoid such combinations until this is fixed.
ImportRt, export and the identity fields are planned together with System.Identity on CK language 2. Until then do not rely on Hidden for values that must not leave the database through those paths. For credentials use the Secret value type, which is encrypted at rest.
Hidden on archived attributes
Rule: Hidden attributes are never archived. The entity access guards of the GraphQL API do not apply to stream data (CrateDB), so the engine keeps Hidden values out of archives. Because archives are runtime entities, the rule is enforced at runtime, not by the compiler:
- an archive cannot be activated (or retried, re-provisioned) when a captured column reaches a Hidden attribute — directly, or as a whole record (or record array) that contains a Hidden sub-attribute at any depth. Computed and rollup columns are not checked;
- as a second line of defence, the column builder refuses Hidden columns and leaves Hidden sub-attributes out of stored records;
- stream-data queries refuse columns, filters, sorts and group-bys that reach a Hidden attribute.
After a model change. Data is not checked at ingest. Instead, active archives are re-checked after each CK model import into the tenant: an active archive that now captures a Hidden attribute is set to Failed. A Failed archive refuses all inserts and all queries — also of its visible columns — until it is activated again (RetryActivation), which only succeeds once the Hidden attribute is no longer captured. The history is kept. In this phase the re-check runs only in services that use stream data, and there is no operation to drop a single captured column. This is a known limitation that will be addressed before System models use Hidden on archived attributes.
Design archives so that they never capture credentials or other Hidden values; use Secret for credentials.
access and SemVer
access is ordered ReadWrite < ReadOnly < MethodOnly < Hidden. Making an assignment's access stricter is
MAJOR: generic GraphQL clients and dependent models lose read or write access. Making it looser is MINOR.
Both carry an "access/security" note in the changelog.
Security exception. Hiding a credential must not force a major cascade across every dependent model. When the
attribute definition is marked securitySensitive: true in the published version and in the
new one, tightening its access is MINOR and the change requires an acknowledge by the publisher (listed under
"Behavioural changes" in the verdict and the changelog). Every other change of such an attribute — removing it,
changing its type — follows the normal rules.
The build fails with OCTO-CK203 until the publisher acknowledges the change in ckModel.yaml. The error prints the
change key and a ready-to-copy entry:
compatibility:
acknowledge:
- change: "TypeAttribute:Account-1/PasswordHash#Modified:access"
reason: "Close accepted risk R13: password hash readable via GraphQL"
The reason is mandatory, keys are exact (no wildcards), an entry is valid for one release only (OCTO-CK204 for a stale
entry), and an acknowledgement never lowers the required version bump. Details: Versioning rules.
securitySensitive
securitySensitive: true on an attribute definition marks password hashes, security stamps, tokens and 2FA secrets
(ckLanguage: 2, message 90 otherwise; omitted = false). It changes no data and no API; it only decides how a later
access tightening is classified (see above). Setting or clearing it is MINOR. Mark the attribute in an earlier
release than, or the same release as, the access tightening — the exception applies only when the attribute is
security-sensitive in both versions.
attributes:
- id: PasswordHash
valueType: String
securitySensitive: true