diff --git a/.specify/feature.json b/.specify/feature.json deleted file mode 100644 index 1be33c1f1c0..00000000000 --- a/.specify/feature.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "feature_directory": "specs/001-entities-arch-migration" -} diff --git a/backend/infrahub/api/schema.py b/backend/infrahub/api/schema.py index 864fc5f9b30..a16606935a8 100644 --- a/backend/infrahub/api/schema.py +++ b/backend/infrahub/api/schema.py @@ -3,18 +3,29 @@ from typing import TYPE_CHECKING, Any, Sequence from fastapi import APIRouter, Depends, Query, Request +from infrahub_sdk.schema.generated.read import ( + GenericSchemaRead, + NodeSchemaRead, + ProfileSchemaRead, + TemplateSchemaRead, +) +from infrahub_sdk.schema.generated.write import InfrahubSchemaWrite +from infrahub_sdk.schema.validate import validate_schema as validate_write_schema from pydantic import ( BaseModel, Field, + PrivateAttr, computed_field, create_model, model_validator, ) +from pydantic import ValidationError as PydanticValidationError from starlette.responses import JSONResponse from infrahub import lock from infrahub.api.dependencies import get_branch_dep, get_context, get_current_user, get_db, get_permission_manager from infrahub.api.exceptions import SchemaNotValidError +from infrahub.api.schema_write_projection import project_to_write_contract from infrahub.branch.status_checker import BranchStatusChecker from infrahub.core import registry from infrahub.core.account import GlobalPermission @@ -75,35 +86,26 @@ def from_schema(cls, schema: MainSchemaTypes) -> Self: data["relationships"] = [ relationship.model_dump() for relationship in schema.relationships if not relationship.internal_peer ] + # ``kind`` is a computed field on the generated read model (derived from namespace+name); + # only ``hash`` needs to be supplied here since it is computed by the server. data["hash"] = schema.get_hash() return cls(**data) - @model_validator(mode="before") - @classmethod - def set_kind(cls, values: Any) -> Any: - if isinstance(values, dict): - values["kind"] = f"{values['namespace']}{values['name']}" - return values - -class APINodeSchema(NodeSchema, APISchemaMixin): - api_kind: str | None = Field(default=None, alias="kind", validate_default=True) - hash: str +class APINodeSchema(NodeSchemaRead, APISchemaMixin): + pass -class APIGenericSchema(GenericSchema, APISchemaMixin): - api_kind: str | None = Field(default=None, alias="kind", validate_default=True) - hash: str +class APIGenericSchema(GenericSchemaRead, APISchemaMixin): + pass -class APIProfileSchema(ProfileSchema, APISchemaMixin): - api_kind: str | None = Field(default=None, alias="kind", validate_default=True) - hash: str +class APIProfileSchema(ProfileSchemaRead, APISchemaMixin): + pass -class APITemplateSchema(TemplateSchema, APISchemaMixin): - api_kind: str | None = Field(default=None, alias="kind", validate_default=True) - hash: str +class APITemplateSchema(TemplateSchemaRead, APISchemaMixin): + pass class SchemaReadAPI(BaseModel): @@ -115,8 +117,71 @@ class SchemaReadAPI(BaseModel): namespaces: list[SchemaNamespace] = Field(default_factory=list) -class SchemaLoadAPI(SchemaRoot): +def _format_schema_errors(errors: list[Any]) -> list[str]: + """Render internal-schema validation errors as field-level messages. + + The dotted path indexes list elements with ``[i]`` (``nodes[0].relationships[0].cardinality``) + and appends the offending scalar value so the message names both the field and what was received. + """ + messages: list[str] = [] + for error in errors: + parts: list[str] = [] + for element in error["loc"]: + if isinstance(element, int) and parts: + parts[-1] = f"{parts[-1]}[{element}]" + else: + parts.append(str(element)) + message = f"{'.'.join(parts)}: {error['msg']}" + received = error.get("input") + if isinstance(received, (str, int, float, bool)) or received is None: + message += f" (received: {received!r})" + messages.append(message) + return messages + + +class SchemaLoadAPI(InfrahubSchemaWrite): version: str + _internal_schema: SchemaRoot = PrivateAttr() + + @model_validator(mode="before") + @classmethod + def validate_write_contract(cls, data: Any) -> Any: + # A submitted schema may legitimately carry read-only/internal fields — a schema read back + # from Infrahub, or a full internal dump. The internal schema accepts those but still + # rejects fields that are unknown or invalid for their context (a typo, or a node-only + # field on an extension), drawing the tolerate/reject line a uniform extra="forbid" cannot. + if isinstance(data, dict): + try: + SchemaRoot.model_validate(data) + except PydanticValidationError as exc: + # Only reject genuinely unknown/forbidden fields here. Constraint and enum + # violations, reserved keywords, and other invariants are surfaced with their + # original messages by the write-contract check below or by build_internal_schema. + unknown = [error for error in exc.errors() if error["type"] == "extra_forbidden"] + if unknown: + raise ValueError("; ".join(_format_schema_errors(unknown))) from exc + + # Drop the read-only/internal fields so the strict write models accept the payload; the + # unknown-field check above has already run, so nothing is hidden. What remains is the + # user-facing write contract, validated for out-of-range constrained values. + data = project_to_write_contract(data) + result = validate_write_schema(schema=data) + if not result.valid: + raise ValueError("; ".join(result.messages)) + return data + + @model_validator(mode="after") + def build_internal_schema(self) -> Self: + # The published request contract only exposes user-settable fields; the processing + # engine operates on the richer internal SchemaRoot. Building it here surfaces + # internal-only invariants (e.g. reserved keywords) as request-validation errors, + # and lets the endpoints feed the pipeline without re-deriving the conversion. + self._internal_schema = SchemaRoot.model_validate(self.model_dump(exclude_none=True)) + return self + + @property + def internal_schema(self) -> SchemaRoot: + return self._internal_schema class SchemasLoadAPI(BaseModel): @@ -170,11 +235,11 @@ def _merge_candidate_schemas(schemas: Sequence[SchemaRoot]) -> SchemaRoot: def evaluate_candidate_schemas( - branch_schema: SchemaBranch, schemas_to_evaluate: SchemasLoadAPI + branch_schema: SchemaBranch, schemas_to_evaluate: Sequence[SchemaRoot] ) -> tuple[SchemaBranch, SchemaUpdateValidationResult]: candidate_schema = branch_schema.duplicate() try: - schema = _merge_candidate_schemas(schemas=schemas_to_evaluate.schemas) + schema = _merge_candidate_schemas(schemas=schemas_to_evaluate) candidate_schema.load_schema(schema=schema) candidate_schema.process() @@ -357,10 +422,13 @@ async def load_schema( errors: list[str] = [] warnings: list[SchemaWarning] = [] + candidate_schemas: list[SchemaRoot] = [] for schema in schemas.schemas: - errors += schema.validate_namespaces() - errors += schema.validate_reserved_suffixes() - warnings += schema.gather_warnings() + internal_schema = schema.internal_schema + candidate_schemas.append(internal_schema) + errors += internal_schema.validate_namespaces() + errors += internal_schema.validate_reserved_suffixes() + warnings += internal_schema.gather_warnings() if errors: raise SchemaNotValidError(message=", ".join(errors)) @@ -369,7 +437,9 @@ async def load_schema( branch_schema = registry.schema.get_schema_branch(name=branch.name) original_hash = branch_schema.get_hash() - candidate_schema, result = evaluate_candidate_schemas(branch_schema=branch_schema, schemas_to_evaluate=schemas) + candidate_schema, result = evaluate_candidate_schemas( + branch_schema=branch_schema, schemas_to_evaluate=candidate_schemas + ) if not result.diff.all: return SchemaUpdate(hash=original_hash, previous_hash=original_hash, diff=result.diff) @@ -445,17 +515,22 @@ async def check_schema( errors: list[str] = [] warnings: list[SchemaWarning] = [] + candidate_schemas: list[SchemaRoot] = [] for schema in schemas.schemas: - errors += schema.validate_namespaces() - errors += schema.validate_reserved_suffixes() - warnings += schema.gather_warnings() + internal_schema = schema.internal_schema + candidate_schemas.append(internal_schema) + errors += internal_schema.validate_namespaces() + errors += internal_schema.validate_reserved_suffixes() + warnings += internal_schema.gather_warnings() if errors: raise SchemaNotValidError(message=", ".join(errors)) branch_schema = registry.schema.get_schema_branch(name=branch.name) - candidate_schema, result = evaluate_candidate_schemas(branch_schema=branch_schema, schemas_to_evaluate=schemas) + candidate_schema, result = evaluate_candidate_schemas( + branch_schema=branch_schema, schemas_to_evaluate=candidate_schemas + ) # ---------------------------------------------------------- # Validate if the new schema is valid with the content of the database diff --git a/backend/infrahub/api/schema_write_projection.py b/backend/infrahub/api/schema_write_projection.py new file mode 100644 index 00000000000..85560e09598 --- /dev/null +++ b/backend/infrahub/api/schema_write_projection.py @@ -0,0 +1,95 @@ +"""Project a schema-load payload onto the user-facing write contract. + +A load payload may carry read-only and internal fields (a schema read back from Infrahub, or a +full internal model dump used by tests). Those fields are legitimate — they are validated against +the internal schema first — but the strict write models reject them. This projection keeps only the +write-settable fields, resolving the per-kind attribute and computed-attribute discriminated unions +so each kind keeps the fields valid for it. It runs only after the internal validation has already +rejected genuinely unknown or forbidden fields, so dropping non-write keys never hides a mistake. +""" + +from __future__ import annotations + +import functools +import types +import typing +from typing import Any + +from infrahub_sdk.schema.generated.write import InfrahubSchemaWrite +from pydantic import BaseModel +from pydantic.fields import FieldInfo + +_UNION_ORIGINS = {typing.Union, types.UnionType} +_SEQUENCE_ORIGINS = {list, tuple} + + +def project_to_write_contract(schema: dict[str, Any]) -> dict[str, Any]: + """Return a copy of ``schema`` containing only fields accepted by the write contract.""" + if not isinstance(schema, dict): + return schema + return _project_model(schema, InfrahubSchemaWrite) + + +@functools.cache +def _resolved_hints(model: type[BaseModel]) -> dict[str, Any]: + # ``model_fields[...].annotation`` leaves module-level Annotated aliases (the discriminated + # unions) as unresolved forward references; ``get_type_hints`` resolves them with the metadata. + return typing.get_type_hints(model, include_extras=True) + + +def _project_model(data: Any, model: type[BaseModel]) -> Any: + if not isinstance(data, dict): + return data + hints = _resolved_hints(model) + projected: dict[str, Any] = {} + for name, info in model.model_fields.items(): + key = name if name in data else (info.alias if info.alias in data else None) + if key is None: + continue + discriminator = info.discriminator if isinstance(info.discriminator, str) else None + projected[key] = _project_value(data[key], hints.get(name, info.annotation), discriminator) + return projected + + +def _project_value(value: Any, annotation: Any, discriminator: str | None = None) -> Any: + # Annotated[...] (used for the discriminated unions): unwrap to the base + its discriminator. + if hasattr(annotation, "__metadata__"): + nested = next( + ( + m.discriminator + for m in annotation.__metadata__ + if isinstance(m, FieldInfo) and isinstance(m.discriminator, str) + ), + None, + ) + return _project_value(value, annotation.__origin__, nested or discriminator) + + origin = typing.get_origin(annotation) + args = typing.get_args(annotation) + + if origin in _UNION_ORIGINS: + members = [a for a in args if a is not type(None)] + if discriminator and isinstance(value, dict): + member = _pick_variant(members, discriminator, value) + if member is not None: + return _project_model(value, member) + return _project_value(value, members[0]) if len(members) == 1 else value + + if origin in _SEQUENCE_ORIGINS and args and isinstance(value, list): + return [_project_value(item, args[0]) for item in value] + + if isinstance(annotation, type) and issubclass(annotation, BaseModel): + return _project_model(value, annotation) + + return value + + +def _pick_variant(members: list[Any], discriminator: str, value: dict[str, Any]) -> type[BaseModel] | None: + target = str(getattr(value.get(discriminator), "value", value.get(discriminator))) + for member in members: + if not (isinstance(member, type) and issubclass(member, BaseModel)): + continue + field = member.model_fields.get(discriminator) + if field is not None and target in {str(getattr(a, "value", a)) for a in typing.get_args(field.annotation)}: + return member + return None diff --git a/backend/infrahub/core/constants/__init__.py b/backend/infrahub/core/constants/__init__.py index 94bcd2ee656..f35aa0fe398 100644 --- a/backend/infrahub/core/constants/__init__.py +++ b/backend/infrahub/core/constants/__init__.py @@ -6,7 +6,14 @@ from infrahub.exceptions import ValidationError from infrahub.utils import InfrahubNumberEnum, InfrahubStringEnum -from .schema import FlagProperty, NodeProperty, SchemaElementPathType, UpdateSupport, UpdateValidationErrorType +from .schema import ( + FlagProperty, + NodeProperty, + SchemaElementPathType, + UpdateSupport, + UpdateValidationErrorType, + Visibility, +) __all__ = [ "FlagProperty", @@ -16,6 +23,7 @@ "UpdateSupport", "UpdateValidationErrorType", "ValidationError", + "Visibility", ] diff --git a/backend/infrahub/core/constants/schema.py b/backend/infrahub/core/constants/schema.py index 98a0c95be73..d7ea0138860 100644 --- a/backend/infrahub/core/constants/schema.py +++ b/backend/infrahub/core/constants/schema.py @@ -1,4 +1,4 @@ -from enum import Enum, Flag, auto +from enum import Enum, Flag, IntEnum, auto PARENT_CHILD_IDENTIFIER = "parent__child" RESOURCE_POOL_REL_SUFFIX = "_from_resource_pool" @@ -21,6 +21,18 @@ class UpdateSupport(Enum): NOT_APPLICABLE = "not_applicable" +class Visibility(IntEnum): + """Ordinal classification of who may see or set a schema field. + + The ordering is meaningful: a field is included in a given model variant when the + variant's minimum level is at or below the field's visibility (write ⊆ read ⊆ internal). + """ + + INTERNAL = 0 + READ = 1 + WRITE = 2 + + class UpdateValidationErrorType(Enum): NOT_SUPPORTED = "not_supported" VALIDATOR_FAILED = "validator_failed" diff --git a/backend/infrahub/core/schema/definitions/internal.py b/backend/infrahub/core/schema/definitions/internal.py index e3fe54b194f..7613ffebf04 100644 --- a/backend/infrahub/core/schema/definitions/internal.py +++ b/backend/infrahub/core/schema/definitions/internal.py @@ -7,7 +7,7 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -from typing_extensions import TypedDict +from typing_extensions import NotRequired, TypedDict from infrahub.core.constants import ( DEFAULT_DESCRIPTION_LENGTH, @@ -31,6 +31,7 @@ RelationshipKind, SchemaAttributeDisplay, UpdateSupport, + Visibility, ) from infrahub.core.schema.attribute_parameters import ( AttributeParameters, @@ -48,6 +49,7 @@ class ExtraField(TypedDict): update: UpdateSupport + visibility: NotRequired[Visibility] class SchemaAttribute(BaseModel): @@ -63,6 +65,10 @@ class SchemaAttribute(BaseModel): min_length: int | None = None max_length: int | None = None enum: list[str] | None = None + enum_class: str | None = Field( + default=None, + description="Name of the generated SDK enum class that carries this field's constrained values.", + ) default_value: Any | None = None default_factory: str | None = None default_to_none: bool = False @@ -76,13 +82,24 @@ class SchemaAttribute(BaseModel): def to_dict(self) -> dict[str, Any]: model = self.model_dump( exclude_none=True, - exclude={"default_factory", "default_to_none", "extra", "internal_kind", "override_default_value"}, + exclude={ + "default_factory", + "default_to_none", + "extra", + "internal_kind", + "override_default_value", + "enum_class", + }, ) if self.default_value is not None and isinstance(self.default_value, Enum): model["default_value"] = self.default_value.value return model + @property + def visibility(self) -> Visibility: + return self.extra.get("visibility", Visibility.INTERNAL) + @property def optional_in_model(self) -> bool: if (self.optional and self.default_value is None and self.default_factory is None) or self.default_to_none: @@ -139,12 +156,113 @@ def default_definition(self) -> str: return f"default={formatted_default}" + @property + def external_object_kind(self) -> str: + """Self-contained type annotation for the user-facing (SDK) write/read models. + + Constrained fields are typed with a dedicated ``(str, Enum)`` class emitted alongside + the models, so the allowed-value set is carried by a self-contained enum rather than an + inline ``Literal``. Backend-only complex kinds collapse to plain JSON-compatible + containers so the models import with only the SDK installed. + """ + if self.enum: + if self.enum_class is not None: + return self.enum_class + values = ", ".join(repr(value) for value in self.enum) + return f"Literal[{values}]" + if self.internal_kind is not None: + return self._external_internal_kind() + + kind_map = { + "Any": "Any", + "Boolean": "bool", + "Text": "str", + "List": "list", + "Number": "int", + "URL": "str", + "JSON": "dict[str, Any]", + } + return kind_map[self.kind] + + def _external_internal_kind(self) -> str: + fixed_types: dict[Any, str] = { + AttributeSchema: "list[AttributeSchema__VARIANT__]", + RelationshipSchema: "list[RelationshipSchema__VARIANT__]", + DropdownChoice: "list[DropdownChoice__VARIANT__]", + ComputedAttribute: "ComputedAttribute__VARIANT__", + } + internal = self.internal_kind + if isinstance(internal, list): + return "AttributeParametersUnion__VARIANT__" + if isinstance(internal, GenericAlias): + return str(internal) + if internal in fixed_types: + return fixed_types[internal] + if internal is None: + return "Any" + if self.kind == "List": + return f"list[{internal.__name__}]" + return internal.__name__ + + @property + def external_is_optional(self) -> bool: + if self.optional_in_model: + return True + # A non-list factory (e.g. a rich parameters model) is not reproducible in a + # self-contained model, so the field is exposed as nullable instead. + if self.default_factory and self.default_factory != "list": + return True + return False + + @property + def external_type_annotation(self) -> str: + if self.external_is_optional: + return f"{self.external_object_kind} | None" + return self.external_object_kind + + @property + def external_default_definition(self) -> str: + if self.default_factory == "list": + return "default_factory=list" + if self.default_factory: + return "default=None" + if not self.optional and self.default_value is None: + return "..." + if self.override_default_value is not None: + return f"default={self.override_default_value}" + return self._external_formatted_default() + + def _external_formatted_default(self) -> str: + default = self.default_value + if self.enum_class is not None and isinstance(default, Enum): + # The field is typed with the generated enum, so its default must be the enum member + # (not the raw value) to type-check; ``use_enum_values`` coerces it to the value at runtime. + return f"default={self.enum_class}.{default.name}" + if isinstance(default, Enum): + return f"default={default.value!r}" + if isinstance(default, str): + return f"default={default!r}" + if self.default_to_none: + return "default=None" + return f"default={default}" + @property def pattern(self) -> str: if self.regex: return f"pattern='{self.regex}'," return "" + @property + def external_pattern(self) -> str: + """Pattern rendering for the self-contained SDK models. + + Emitted as a raw string literal so regex escapes such as ``\\b`` keep their + regular-expression meaning instead of being parsed as string escapes. + """ + if self.regex: + return f'pattern=r"{self.regex}",' + return "" + @property def min(self) -> str: if self.min_length is not None: @@ -169,9 +287,16 @@ class SchemaRelationship(BaseModel): cardinality: str branch: str optional: bool + extra: ExtraField | None = None def to_dict(self) -> dict[str, Any]: - return self.model_dump(exclude_none=True) + return self.model_dump(exclude_none=True, exclude={"extra"}) + + @property + def visibility(self) -> Visibility: + if self.extra is None: + return Visibility.INTERNAL + return self.extra.get("visibility", Visibility.INTERNAL) class SchemaNode(BaseModel): @@ -237,7 +362,7 @@ def to_dict(self) -> dict[str, Any]: description="The ID of the node", kind="Text", optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="name", @@ -247,7 +372,7 @@ def to_dict(self) -> dict[str, Any]: regex=str(NODE_NAME_REGEX), min_length=DEFAULT_NAME_MIN_LENGTH, max_length=DEFAULT_NAME_MAX_LENGTH, - extra={"update": UpdateSupport.MIGRATION_REQUIRED}, + extra={"update": UpdateSupport.MIGRATION_REQUIRED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="namespace", @@ -256,7 +381,7 @@ def to_dict(self) -> dict[str, Any]: regex=str(NAMESPACE_REGEX), min_length=DEFAULT_KIND_MIN_LENGTH, max_length=DEFAULT_KIND_MAX_LENGTH, - extra={"update": UpdateSupport.MIGRATION_REQUIRED}, + extra={"update": UpdateSupport.MIGRATION_REQUIRED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="description", @@ -264,7 +389,7 @@ def to_dict(self) -> dict[str, Any]: description="Short description of the model, will be visible in the frontend.", optional=True, max_length=DEFAULT_DESCRIPTION_LENGTH, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="label", @@ -272,7 +397,7 @@ def to_dict(self) -> dict[str, Any]: description="Human friendly representation of the name/kind", optional=True, max_length=DEFAULT_LABEL_MAX_LENGTH, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="branch", @@ -280,9 +405,13 @@ def to_dict(self) -> dict[str, Any]: internal_kind=BranchSupportType, description="Type of branch support for the model.", enum=BranchSupportType.available_types(), + enum_class="BranchSupportType", default_value=BranchSupportType.AWARE, optional=True, - extra={"update": UpdateSupport.NOT_SUPPORTED}, # https://github.com/opsmill/infrahub/issues/2477 + extra={ + "update": UpdateSupport.NOT_SUPPORTED, + "visibility": Visibility.WRITE, + }, # https://github.com/opsmill/infrahub/issues/2477 ), SchemaAttribute( name="default_filter", @@ -290,7 +419,7 @@ def to_dict(self) -> dict[str, Any]: regex=str(NAME_REGEX_OR_EMPTY), description="Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)", optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="human_friendly_id", @@ -299,14 +428,14 @@ def to_dict(self) -> dict[str, Any]: description="Human friendly and unique identifier for the object.", optional=True, ordered=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="display_label", kind="Text", description="Attribute or Jinja2 template to use to generate the display label", optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="display_labels", @@ -315,7 +444,7 @@ def to_dict(self) -> dict[str, Any]: description="List of attributes to use to generate the display label (deprecated)", optional=True, ordered=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="include_in_menu", @@ -324,21 +453,21 @@ def to_dict(self) -> dict[str, Any]: default_value=True, default_to_none=True, optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="menu_placement", kind="Text", description="Defines where in the menu this object should be placed.", optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="icon", kind="Text", description="Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/", optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="order_by", @@ -350,7 +479,7 @@ def to_dict(self) -> dict[str, Any]: ), optional=True, ordered=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="uniqueness_constraints", @@ -359,14 +488,14 @@ def to_dict(self) -> dict[str, Any]: description="List of multi-element uniqueness constraints that can combine relationships and attributes", optional=True, ordered=True, - extra={"update": UpdateSupport.MIGRATION_REQUIRED}, + extra={"update": UpdateSupport.MIGRATION_REQUIRED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="documentation", kind="URL", description="Link to a documentation associated with this object, can be internal or external.", optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="state", @@ -375,8 +504,9 @@ def to_dict(self) -> dict[str, Any]: description="Expected state of the node/generic after loading the schema", default_value=HashableModelState.PRESENT, enum=HashableModelState.available_types(), + enum_class="SchemaState", optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="attributes", @@ -385,7 +515,7 @@ def to_dict(self) -> dict[str, Any]: description="Node attributes", default_factory="list", optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="relationships", @@ -394,7 +524,7 @@ def to_dict(self) -> dict[str, Any]: description="Node Relationships", default_factory="list", optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), ], relationships=[], @@ -417,7 +547,7 @@ def to_dict(self) -> dict[str, Any]: default_factory="list", description="List of Generic Kind that this node is inheriting from", optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="generate_profile", @@ -425,7 +555,7 @@ def to_dict(self) -> dict[str, Any]: description="Indicate if a profile schema should be generated for this schema", default_value=True, optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="generate_template", @@ -433,28 +563,28 @@ def to_dict(self) -> dict[str, Any]: description="Indicate if an object template schema should be generated for this schema", default_value=False, optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="hierarchy", kind="Text", description="Internal value to track the name of the Hierarchy, must match the name of a Generic supporting hierarchical mode", optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.READ}, ), SchemaAttribute( name="parent", kind="Text", description="Expected Kind for the parent node in a Hierarchy, default to the main generic defined if not defined.", optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="children", kind="Text", description="Expected Kind for the children nodes in a Hierarchy, default to the main generic defined if not defined.", optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), ], relationships=[ @@ -467,6 +597,7 @@ def to_dict(self) -> dict[str, Any]: cardinality="many", branch=BranchSupportType.AWARE.value, optional=True, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), SchemaRelationship( name="relationships", @@ -477,6 +608,7 @@ def to_dict(self) -> dict[str, Any]: cardinality="many", branch=BranchSupportType.AWARE.value, optional=True, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), ], ) @@ -495,7 +627,7 @@ def to_dict(self) -> dict[str, Any]: description="The ID of the attribute", kind="Text", optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="name", @@ -504,14 +636,15 @@ def to_dict(self) -> dict[str, Any]: regex=str(NAME_REGEX), min_length=DEFAULT_KIND_MIN_LENGTH, max_length=DEFAULT_KIND_MAX_LENGTH, - extra={"update": UpdateSupport.MIGRATION_REQUIRED}, + extra={"update": UpdateSupport.MIGRATION_REQUIRED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="kind", kind="Text", description="Defines the type of the attribute.", enum=ATTRIBUTE_KIND_LABELS, - extra={"update": UpdateSupport.MIGRATION_REQUIRED}, + enum_class="AttributeKind", + extra={"update": UpdateSupport.MIGRATION_REQUIRED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="enum", @@ -519,7 +652,7 @@ def to_dict(self) -> dict[str, Any]: description="Define a list of valid values for the attribute.", optional=True, ordered=False, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="computed_attribute", @@ -527,7 +660,7 @@ def to_dict(self) -> dict[str, Any]: internal_kind=ComputedAttribute, description="Defines how the value of this attribute will be populated.", optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="choices", @@ -536,28 +669,28 @@ def to_dict(self) -> dict[str, Any]: description="Define a list of valid choices for a dropdown attribute.", optional=True, ordered=False, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="regex", kind="Text", description="Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)", optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="max_length", kind="Number", description="Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)", optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="min_length", kind="Number", description="Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)", optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="label", @@ -565,7 +698,7 @@ def to_dict(self) -> dict[str, Any]: optional=True, description="Human friendly representation of the name. Will be autogenerated if not provided", max_length=DEFAULT_LABEL_MAX_LENGTH, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="description", @@ -573,7 +706,7 @@ def to_dict(self) -> dict[str, Any]: optional=True, description="Short description of the attribute.", max_length=DEFAULT_DESCRIPTION_LENGTH, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="read_only", @@ -582,7 +715,7 @@ def to_dict(self) -> dict[str, Any]: "Mainly relevant for internal object.", default_value=False, optional=True, - extra={"update": UpdateSupport.MIGRATION_REQUIRED}, + extra={"update": UpdateSupport.MIGRATION_REQUIRED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="unique", @@ -590,7 +723,7 @@ def to_dict(self) -> dict[str, Any]: description="Indicate if the value of this attribute must be unique in the database for a given model.", default_value=False, optional=True, - extra={"update": UpdateSupport.MIGRATION_REQUIRED}, + extra={"update": UpdateSupport.MIGRATION_REQUIRED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="optional", @@ -599,7 +732,7 @@ def to_dict(self) -> dict[str, Any]: default_value=False, override_default_value=False, optional=True, - extra={"update": UpdateSupport.MIGRATION_REQUIRED}, + extra={"update": UpdateSupport.MIGRATION_REQUIRED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="branch", @@ -607,15 +740,19 @@ def to_dict(self) -> dict[str, Any]: internal_kind=BranchSupportType, description="Type of branch support for the attribute, if not defined it will be inherited from the node.", enum=BranchSupportType.available_types(), + enum_class="BranchSupportType", optional=True, - extra={"update": UpdateSupport.NOT_SUPPORTED}, # https://github.com/opsmill/infrahub/issues/2475 + extra={ + "update": UpdateSupport.NOT_SUPPORTED, + "visibility": Visibility.WRITE, + }, # https://github.com/opsmill/infrahub/issues/2475 ), SchemaAttribute( name="order_weight", kind="Number", description="Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first.", optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="ordered", @@ -624,14 +761,14 @@ def to_dict(self) -> dict[str, Any]: "or JSON-array attribute is not a merge/rebase conflict.", default_value=True, optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="default_value", kind="Any", description="Default value of the attribute.", optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="inherited", @@ -639,7 +776,7 @@ def to_dict(self) -> dict[str, Any]: default_value=False, description="Internal value to indicate if the attribute was inherited from a Generic node.", optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.READ}, ), SchemaAttribute( name="state", @@ -648,8 +785,9 @@ def to_dict(self) -> dict[str, Any]: description="Expected state of the attribute after loading the schema", default_value=HashableModelState.PRESENT, enum=HashableModelState.available_types(), + enum_class="SchemaState", optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="allow_override", @@ -657,9 +795,10 @@ def to_dict(self) -> dict[str, Any]: internal_kind=AllowOverrideType, description="Type of allowed override for the attribute.", enum=AllowOverrideType.available_types(), + enum_class="AllowOverrideType", default_value=AllowOverrideType.ANY, optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="parameters", @@ -673,7 +812,7 @@ def to_dict(self) -> dict[str, Any]: ], optional=True, description="Extra parameters specific to this kind of attribute", - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, default_factory="AttributeParameters", ), SchemaAttribute( @@ -682,7 +821,7 @@ def to_dict(self) -> dict[str, Any]: optional=True, description="Mark attribute as deprecated and provide a user-friendly message to display", max_length=DEFAULT_DESCRIPTION_LENGTH, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="display", @@ -693,9 +832,10 @@ def to_dict(self) -> dict[str, Any]: "'extra' shows in an expanded/secondary section." ), enum=SchemaAttributeDisplay.available_types(), + enum_class="SchemaAttributeDisplay", default_value=SchemaAttributeDisplay.DEFAULT, optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), ], relationships=[ @@ -707,6 +847,7 @@ def to_dict(self) -> dict[str, Any]: cardinality="one", branch=BranchSupportType.AWARE.value, optional=False, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.INTERNAL}, ) ], ) @@ -725,7 +866,7 @@ def to_dict(self) -> dict[str, Any]: description="The ID of the relationship schema", kind="Text", optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="name", @@ -734,14 +875,14 @@ def to_dict(self) -> dict[str, Any]: regex=str(NAME_REGEX), min_length=DEFAULT_KIND_MIN_LENGTH, max_length=DEFAULT_KIND_MAX_LENGTH, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="peer", kind="Text", description="Type (kind) of objects supported on the other end of the relationship.", regex=str(NODE_KIND_REGEX), - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="kind", @@ -749,8 +890,9 @@ def to_dict(self) -> dict[str, Any]: internal_kind=RelationshipKind, description="Defines the type of the relationship.", enum=RelationshipKind.available_types(), + enum_class="RelationshipKind", default_value=RelationshipKind.GENERIC, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="label", @@ -758,7 +900,7 @@ def to_dict(self) -> dict[str, Any]: description="Human friendly representation of the name. Will be autogenerated if not provided", optional=True, max_length=DEFAULT_LABEL_MAX_LENGTH, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="description", @@ -766,7 +908,7 @@ def to_dict(self) -> dict[str, Any]: optional=True, description="Short description of the relationship.", max_length=DEFAULT_DESCRIPTION_LENGTH, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="identifier", @@ -776,7 +918,7 @@ def to_dict(self) -> dict[str, Any]: regex=str(NAME_REGEX), max_length=DEFAULT_REL_IDENTIFIER_LENGTH, optional=True, - extra={"update": UpdateSupport.NOT_SUPPORTED}, + extra={"update": UpdateSupport.NOT_SUPPORTED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="cardinality", @@ -784,9 +926,10 @@ def to_dict(self) -> dict[str, Any]: internal_kind=RelationshipCardinality, description="Defines how many objects are expected on the other side of the relationship.", enum=RelationshipCardinality.available_types(), + enum_class="RelationshipCardinality", default_value=RelationshipCardinality.MANY, optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="min_count", @@ -794,7 +937,7 @@ def to_dict(self) -> dict[str, Any]: description="Defines the minimum objects allowed on the other side of the relationship.", default_value=0, optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="max_count", @@ -802,14 +945,14 @@ def to_dict(self) -> dict[str, Any]: description="Defines the maximum objects allowed on the other side of the relationship.", default_value=0, optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="common_parent", kind="Text", optional=True, description="Name of a parent relationship on the peer schema that must share the same related object with the object's parent.", - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="common_relatives", @@ -817,14 +960,14 @@ def to_dict(self) -> dict[str, Any]: internal_kind=str, optional=True, description="List of relationship names on the peer schema for which all objects must share the same set of peers.", - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="order_weight", kind="Number", description="Number used to order the relationship in the frontend (table and view). Lowest value will be ordered first.", optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="optional", @@ -833,7 +976,7 @@ def to_dict(self) -> dict[str, Any]: default_value=False, override_default_value=True, optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="branch", @@ -841,8 +984,12 @@ def to_dict(self) -> dict[str, Any]: internal_kind=BranchSupportType, description="Type of branch support for the relationship. If not defined, it will be determined based on both peers.", enum=BranchSupportType.available_types(), + enum_class="BranchSupportType", optional=True, - extra={"update": UpdateSupport.NOT_SUPPORTED}, # https://github.com/opsmill/infrahub/issues/2476 + extra={ + "update": UpdateSupport.NOT_SUPPORTED, + "visibility": Visibility.WRITE, + }, # https://github.com/opsmill/infrahub/issues/2476 ), SchemaAttribute( name="inherited", @@ -850,7 +997,7 @@ def to_dict(self) -> dict[str, Any]: description="Internal value to indicate if the relationship was inherited from a Generic node.", default_value=False, optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.READ}, ), SchemaAttribute( name="direction", @@ -859,16 +1006,23 @@ def to_dict(self) -> dict[str, Any]: description="Defines the direction of the relationship, " " Unidirectional relationship are required when the same model is on both side.", enum=RelationshipDirection.available_types(), + enum_class="RelationshipDirection", default_value=RelationshipDirection.BIDIR, optional=True, - extra={"update": UpdateSupport.NOT_SUPPORTED}, # https://github.com/opsmill/infrahub/issues/2471 + extra={ + "update": UpdateSupport.NOT_SUPPORTED, + "visibility": Visibility.WRITE, + }, # https://github.com/opsmill/infrahub/issues/2471 ), SchemaAttribute( name="hierarchical", kind="Text", description="Internal attribute to track the type of hierarchy this relationship is part of, must match a valid Generic Kind", optional=True, - extra={"update": UpdateSupport.NOT_SUPPORTED}, # https://github.com/opsmill/infrahub/issues/2596 + extra={ + "update": UpdateSupport.NOT_SUPPORTED, + "visibility": Visibility.READ, + }, # https://github.com/opsmill/infrahub/issues/2596 ), SchemaAttribute( name="state", @@ -877,8 +1031,9 @@ def to_dict(self) -> dict[str, Any]: description="Expected state of the relationship after loading the schema", default_value=HashableModelState.PRESENT, enum=HashableModelState.available_types(), + enum_class="SchemaState", optional=True, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="on_delete", @@ -886,9 +1041,10 @@ def to_dict(self) -> dict[str, Any]: internal_kind=RelationshipDeleteBehavior, description="Default is no-action. If cascade, related node(s) are deleted when this node is deleted.", enum=RelationshipDeleteBehavior.available_types(), + enum_class="RelationshipDeleteBehavior", default_value=None, optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="allow_override", @@ -896,9 +1052,10 @@ def to_dict(self) -> dict[str, Any]: internal_kind=AllowOverrideType, description="Type of allowed override for the relationship.", enum=AllowOverrideType.available_types(), + enum_class="AllowOverrideType", default_value=AllowOverrideType.ANY, optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="read_only", @@ -906,7 +1063,7 @@ def to_dict(self) -> dict[str, Any]: description="Set the relationship as read-only, users won't be able to change its value.", default_value=False, optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="deprecation", @@ -914,7 +1071,7 @@ def to_dict(self) -> dict[str, Any]: optional=True, description="Mark relationship as deprecated and provide a user-friendly message to display", max_length=DEFAULT_DESCRIPTION_LENGTH, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="display", @@ -925,9 +1082,10 @@ def to_dict(self) -> dict[str, Any]: "'extra' shows in an expanded/secondary section." ), enum=SchemaAttributeDisplay.available_types(), + enum_class="SchemaAttributeDisplay", default_value=SchemaAttributeDisplay.DEFAULT, optional=True, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), ], relationships=[ @@ -939,6 +1097,7 @@ def to_dict(self) -> dict[str, Any]: cardinality="one", branch=BranchSupportType.AWARE.value, optional=False, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.INTERNAL}, ) ], ) @@ -959,7 +1118,7 @@ def to_dict(self) -> dict[str, Any]: description="Defines if the Generic support the hierarchical mode.", optional=True, default_value=False, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="generate_profile", @@ -967,7 +1126,7 @@ def to_dict(self) -> dict[str, Any]: description="Indicate if a profile schema should be generated for this schema", default_value=True, optional=True, - extra={"update": UpdateSupport.VALIDATE_CONSTRAINT}, + extra={"update": UpdateSupport.VALIDATE_CONSTRAINT, "visibility": Visibility.WRITE}, ), SchemaAttribute( name="used_by", @@ -977,7 +1136,7 @@ def to_dict(self) -> dict[str, Any]: description="List of Nodes that are referencing this Generic", optional=True, ordered=False, - extra={"update": UpdateSupport.NOT_APPLICABLE}, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.READ}, ), SchemaAttribute( name="restricted_namespaces", @@ -986,7 +1145,7 @@ def to_dict(self) -> dict[str, Any]: description="Nodes inheriting from this Generic schema must belong to one of the listed namespaces", optional=True, ordered=False, - extra={"update": UpdateSupport.ALLOWED}, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE}, ), ], relationships=[ @@ -997,6 +1156,7 @@ def to_dict(self) -> dict[str, Any]: cardinality="many", branch=BranchSupportType.AWARE.value, optional=True, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), SchemaRelationship( name="relationships", @@ -1005,6 +1165,7 @@ def to_dict(self) -> dict[str, Any]: cardinality="many", branch=BranchSupportType.AWARE.value, optional=True, + extra={"update": UpdateSupport.NOT_APPLICABLE, "visibility": Visibility.WRITE}, ), ], ) diff --git a/backend/templates/generate_schema_sdk.j2 b/backend/templates/generate_schema_sdk.j2 new file mode 100644 index 00000000000..364851954d9 --- /dev/null +++ b/backend/templates/generate_schema_sdk.j2 @@ -0,0 +1,57 @@ +# Generated by "invoke backend.generate", do not edit directly + +from __future__ import annotations + +from typing import Annotated, Any, Literal, Union + +from pydantic import BaseModel, ConfigDict, Field{% if with_computed_field %}, computed_field{% endif %} + +from .enums import ( +{%- for name in enum_names %} + {{ name }}, +{%- endfor %} +) + +{% for family in pre_families %} + +class {{ family.class_name }}({{ family.parent }}): + model_config = ConfigDict({{ model_config_args }}) +{%- for attribute in family.attributes %} + {{ attribute.name }}: {{ attribute.external_type_annotation }} = Field( + {{ attribute.external_default_definition }}, + description="{{ attribute.description }}", + {{ attribute.external_pattern }} + {{ attribute.min }} + {{ attribute.max }} + ) +{%- endfor %} +{% endfor %} +{% for alias in aliases %} +{{ alias }} +{% endfor %} +{% for family in families %} + +class {{ family.class_name }}({{ family.parent }}): + model_config = ConfigDict({{ model_config_args }}) +{%- for attribute in family.attributes %} + {{ attribute.name }}: {{ attribute.external_type_annotation }} = Field( + {{ attribute.external_default_definition }}, + description="{{ attribute.description }}", + {{ attribute.external_pattern }} + {{ attribute.min }} + {{ attribute.max }} + ) +{%- endfor %} +{%- for computed in family.get("computed_fields", []) %} + +{% if computed.serialize %} @computed_field +{% endif %} @property + def {{ computed.name }}(self) -> {{ computed.return_type }}: + return {{ computed.expression }} +{%- endfor %} +{% endfor %} + +class {{ root.class_name }}(BaseModel): +{%- for field in root.fields %} + {{ field }} +{%- endfor %} diff --git a/backend/templates/generate_schema_sdk_enums.j2 b/backend/templates/generate_schema_sdk_enums.j2 new file mode 100644 index 00000000000..da01921f1ee --- /dev/null +++ b/backend/templates/generate_schema_sdk_enums.j2 @@ -0,0 +1,13 @@ +# Generated by "invoke backend.generate", do not edit directly + +from __future__ import annotations + +from enum import Enum + +{% for enum in enums %} + +class {{ enum.name }}(str, Enum): +{%- for member, value in enum.members %} + {{ member }} = {{ value }} +{%- endfor %} +{% endfor %} diff --git a/backend/tests/component/api/test_40_schema.py b/backend/tests/component/api/test_40_schema.py index b02cef8a8ed..9902745e165 100644 --- a/backend/tests/component/api/test_40_schema.py +++ b/backend/tests/component/api/test_40_schema.py @@ -1,5 +1,13 @@ +import typing + import pytest from fastapi.testclient import TestClient +from infrahub_sdk.schema.generated.read import ( + AttributeSchemaRead, + GenericSchemaRead, + NodeSchemaRead, + RelationshipSchemaRead, +) from infrahub import config from infrahub.core import registry @@ -48,6 +56,73 @@ async def test_schema_read_endpoint_default_branch( assert generics["TestCar"]["used_by"] +async def test_schema_read_endpoint_visibility( + db: InfrahubDatabase, + client: TestClient, + client_headers: dict[str, str], + default_branch: Branch, + car_person_schema_generics: SchemaRoot, + car_person_data_generic: dict[str, Node], +) -> None: + """GET /api/schema exposes read-level fields and never leaks internal or unclassified fields. + + The read-back is serialised consistently with the generated read model: every read-level + field (``inherited``/``used_by`` and the derived hierarchy fields) is present, the internal + parent back-reference (``node``) is never returned, and no field outside the read model's + visibility appears in the payload. ``hash`` and ``kind`` are the only response-level additions. + """ + with client: + response = client.get("/api/schema", headers=client_headers) + + assert response.status_code == 200 + schema = response.json() + + response_additions = {"hash", "kind"} + node_allowed = set(NodeSchemaRead.model_fields) | response_additions + generic_allowed = set(GenericSchemaRead.model_fields) | response_additions + # AttributeSchemaRead is a discriminated union of per-kind variants; the read-back may carry any + # kind, so the allowed-field set is the union of every variant's fields. + attribute_union = typing.get_args(typing.get_args(AttributeSchemaRead)[0]) + attribute_fields: set[str] = set().union(*(set(variant.model_fields) for variant in attribute_union)) + relationship_fields = set(RelationshipSchemaRead.model_fields) + + # A read-level field derived from generic inheritance is present and populated. + generics = {item["kind"]: item for item in schema["generics"]} + assert generics["TestCar"]["used_by"] + + def assert_members_visibility(node: dict) -> bool: + saw_inherited = False + for attribute in node["attributes"]: + unexpected = set(attribute) - attribute_fields + assert not unexpected, f"attribute leaked non-read fields: {unexpected}" + assert "inherited" in attribute # read-level field is visible on read-back + assert "node" not in attribute # internal parent back-reference is never returned + saw_inherited = saw_inherited or bool(attribute["inherited"]) + for relationship in node["relationships"]: + unexpected = set(relationship) - relationship_fields + assert not unexpected, f"relationship leaked non-read fields: {unexpected}" + assert "inherited" in relationship + assert "hierarchical" in relationship # derived read-level field + assert "node" not in relationship + return saw_inherited + + saw_inherited_attribute = False + for node in schema["nodes"]: + unexpected = set(node) - node_allowed + assert not unexpected, f"node leaked non-read fields: {unexpected}" + assert "hierarchy" in node # derived read-level field + saw_inherited_attribute = assert_members_visibility(node) or saw_inherited_attribute + + for generic in schema["generics"]: + unexpected = set(generic) - generic_allowed + assert not unexpected, f"generic leaked non-read fields: {unexpected}" + assert "used_by" in generic + assert_members_visibility(generic) + + # At least one attribute inherited from a generic is flagged with the read-level `inherited`. + assert saw_inherited_attribute + + async def test_schema_read_endpoint_branch1( db: InfrahubDatabase, client: TestClient, diff --git a/backend/tests/fixtures/schemas/core_schema_01.json b/backend/tests/fixtures/schemas/core_schema_01.json index 51c92fe8766..0dc7bbf7970 100644 --- a/backend/tests/fixtures/schemas/core_schema_01.json +++ b/backend/tests/fixtures/schemas/core_schema_01.json @@ -11,7 +11,7 @@ "attributes": [ { "name": "name", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -22,7 +22,7 @@ }, { "name": "location", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -52,7 +52,7 @@ "attributes": [ { "name": "name", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -72,7 +72,7 @@ "attributes": [ { "name": "query", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -83,7 +83,7 @@ }, { "name": "description", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -94,7 +94,7 @@ }, { "name": "name", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -129,7 +129,7 @@ "attributes": [ { "name": "type", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": "LOCAL", @@ -140,7 +140,7 @@ }, { "name": "name", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -151,7 +151,7 @@ }, { "name": "description", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -162,7 +162,7 @@ }, { "name": "commit", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -173,7 +173,7 @@ }, { "name": "location", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -184,7 +184,7 @@ }, { "name": "default_branch", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": "main", @@ -243,7 +243,7 @@ "attributes": [ { "name": "username", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -272,7 +272,7 @@ "attributes": [ { "name": "name", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -283,7 +283,7 @@ }, { "name": "description", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -304,9 +304,9 @@ "namespace": "Testing", "default_filter": "name__value", "attributes": [ - {"name": "name", "kind": "String", "unique": true}, - {"name": "description", "kind": "String", "optional": true}, - {"name": "type", "kind": "String"} + {"name": "name", "kind": "Text", "unique": true}, + {"name": "description", "kind": "Text", "optional": true}, + {"name": "type", "kind": "Text"} ], "relationships": [ {"name": "tags", "peer": "TestingTag", "optional": true, "cardinality": "many"}, diff --git a/backend/tests/fixtures/schemas/schema_01.json b/backend/tests/fixtures/schemas/schema_01.json index f073b76d546..b3aa7aefad0 100644 --- a/backend/tests/fixtures/schemas/schema_01.json +++ b/backend/tests/fixtures/schemas/schema_01.json @@ -7,7 +7,7 @@ "attributes": [ { "name": "query", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -18,7 +18,7 @@ }, { "name": "description", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -29,7 +29,7 @@ }, { "name": "name", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -64,7 +64,7 @@ "attributes": [ { "name": "username", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -75,7 +75,7 @@ }, { "name": "type", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": "LOCAL", @@ -86,7 +86,7 @@ }, { "name": "name", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -97,7 +97,7 @@ }, { "name": "description", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -108,7 +108,7 @@ }, { "name": "commit", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -119,7 +119,7 @@ }, { "name": "location", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -130,7 +130,7 @@ }, { "name": "password", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -141,7 +141,7 @@ }, { "name": "default_branch", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": "main", @@ -152,7 +152,7 @@ }, { "name": "internal_status", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": "main", @@ -201,7 +201,7 @@ "attributes": [ { "name": "name", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -212,7 +212,7 @@ }, { "name": "description", - "kind": "String", + "kind": "Text", "label": null, "description": null, "default_value": null, @@ -235,17 +235,17 @@ "attributes": [ { "name": "name", - "kind": "String", + "kind": "Text", "unique": true }, { "name": "description", - "kind": "String", + "kind": "Text", "optional": true }, { "name": "type", - "kind": "String" + "kind": "Text" } ], "relationships": [ diff --git a/backend/tests/functional/api/test_load_schema.py b/backend/tests/functional/api/test_load_schema.py index 775fb20f3db..33a30fd8d89 100644 --- a/backend/tests/functional/api/test_load_schema.py +++ b/backend/tests/functional/api/test_load_schema.py @@ -4,7 +4,10 @@ import pytest from infrahub_sdk.schema import GenericSchemaAPI as SDKGenericSchema +from infrahub_sdk.schema import validate_schema +from infrahub_sdk.uuidt import UUIDT +from infrahub.core.initialization import create_account from infrahub.core.manager import NodeManager from infrahub.core.metadata.model import MetadataQueryOptions from infrahub.core.query.node import MetadataOptions @@ -26,6 +29,7 @@ from infrahub.database import InfrahubDatabase from tests.adapters.message_bus import BusSimulator from tests.conftest import TestHelper + from tests.helpers.test_client import InfrahubTestClient class TestLoadSchemaAPI(TestInfrahubApp): @@ -596,3 +600,332 @@ async def test_remove_optional_text_field_value( assert schema.documentation is None assert schema.parent == "TestLocation" assert schema.children == "TestLocation" + + async def test_schema_load_rejects_non_write_and_unknown_fields( + self, + initial_dataset: str, + test_client: InfrahubTestClient, + api_admin_token: str, + ) -> None: + """Reject a payload carrying a read-level field plus an unknown field, naming each. + + A read-level field (`inherited`) and an unknown field must both be reported. + """ + payload = { + "schemas": [ + { + "version": "1.0", + "nodes": [ + { + "name": "Device", + "namespace": "Test", + "attributes": [ + { + "name": "name", + "kind": "Text", + "inherited": True, + "not_a_real_field": "value", + } + ], + } + ], + } + ] + } + + response = await test_client.post( + "/api/schema/load", + json=payload, + headers={"X-INFRAHUB-KEY": api_admin_token}, + ) + + assert response.status_code == 422 + body = response.text + assert "inherited" in body + assert "not_a_real_field" in body + + async def test_schema_load_rejects_out_of_enum_attribute_kind( + self, + initial_dataset: str, + test_client: InfrahubTestClient, + api_admin_token: str, + ) -> None: + """Reject a payload setting attribute `kind` to a non-existent value, naming field and value.""" + payload = { + "schemas": [ + { + "version": "1.0", + "nodes": [ + { + "name": "Device", + "namespace": "Test", + "attributes": [{"name": "name", "kind": "NotARealKind"}], + } + ], + } + ] + } + + response = await test_client.post( + "/api/schema/load", + json=payload, + headers={"X-INFRAHUB-KEY": api_admin_token}, + ) + + assert response.status_code == 422 + body = response.text + assert "kind" in body + assert "NotARealKind" in body + + async def test_schema_load_tolerates_read_level_rejects_unknown_fields_in_extensions( + self, + initial_dataset: str, + test_client: InfrahubTestClient, + api_admin_token: str, + ) -> None: + """An extension attribute's read-level field is tolerated; an unknown field is rejected. + + A read-level field (`inherited`) may accompany a schema read back from Infrahub or a full + model dump, so it is stripped rather than rejected. A genuinely unknown field has no such + justification and is still rejected, located under `extensions.nodes[*]`. + """ + payload = { + "schemas": [ + { + "version": "1.0", + "extensions": { + "nodes": [ + { + "kind": "TestDevice", + "attributes": [ + { + "name": "extra", + "kind": "Text", + "inherited": True, + "not_a_real_field": "value", + } + ], + } + ] + }, + } + ] + } + + response = await test_client.post( + "/api/schema/load", + json=payload, + headers={"X-INFRAHUB-KEY": api_admin_token}, + ) + + assert response.status_code == 422 + messages = [item["msg"] for item in response.json()["detail"]] + joined = " ".join(messages) + # The unknown field is rejected and located under the extension attribute. + assert "extensions.nodes[0].attributes[0]" in joined, messages + assert "not_a_real_field" in joined, messages + # `inherited` is a read-level field: it is stripped and tolerated, never reported. + assert "inherited" not in joined, messages + + async def test_schema_load_rejects_out_of_enum_relationship_cardinality( + self, + initial_dataset: str, + test_client: InfrahubTestClient, + api_admin_token: str, + ) -> None: + """Reject a relationship whose `cardinality` is outside its allowed set, naming field and value.""" + payload = { + "schemas": [ + { + "version": "1.0", + "nodes": [ + { + "name": "Device", + "namespace": "Test", + "attributes": [{"name": "name", "kind": "Text"}], + "relationships": [ + {"name": "peers", "peer": "TestDevice", "cardinality": "both", "optional": True} + ], + } + ], + } + ] + } + + response = await test_client.post( + "/api/schema/load", + json=payload, + headers={"X-INFRAHUB-KEY": api_admin_token}, + ) + + assert response.status_code == 422 + messages = [item["msg"] for item in response.json()["detail"]] + joined = " ".join(messages) + assert "nodes[0].relationships[0].cardinality" in joined, messages + assert "both" in joined, messages + + async def test_stored_schema_with_read_level_field_reads_back( + self, + initial_dataset: str, + client: InfrahubClient, + test_client: InfrahubTestClient, + api_admin_token: str, + default_branch: Branch, + ) -> None: + """A stored schema containing now-`read` fields reads back without error. + + Loading a generic and a node that inherits from it produces read-level fields the user + never submitted (`used_by` on the generic, `inherited` on the node's inherited attribute). + Reading the stored schema back must succeed and expose those fields. + """ + schema_dict = { + "version": "1.0", + "generics": [ + { + "name": "Animal", + "namespace": "Test", + "attributes": [{"name": "name", "kind": "Text"}], + } + ], + "nodes": [ + { + "name": "Dog", + "namespace": "Test", + "inherit_from": ["TestAnimal"], + "attributes": [{"name": "breed", "kind": "Text", "optional": True}], + } + ], + } + creation = await client.schema.load(schemas=[schema_dict]) + assert not creation.errors + + response = await test_client.get("/api/schema", headers={"X-INFRAHUB-KEY": api_admin_token}) + assert response.status_code == 200 + schema = response.json() + + generics = {item["kind"]: item for item in schema["generics"]} + assert "TestDog" in generics["TestAnimal"]["used_by"] + + nodes = {item["kind"]: item for item in schema["nodes"]} + dog = nodes["TestDog"] + inherited_attributes = [attr for attr in dog["attributes"] if attr["inherited"]] + assert inherited_attributes # the `name` attribute inherited from TestAnimal reads back as inherited + + async def test_schema_load_id_cannot_bypass_authorization( + self, + initial_dataset: str, + client: InfrahubClient, + test_client: InfrahubTestClient, + helper: TestHelper, + db: InfrahubDatabase, + default_branch: Branch, + ) -> None: + """An `id` in a payload cannot rename/delete an object the caller may not modify. + + A caller without schema-management permission cannot mutate an existing object by + carrying its `id`: authorization is enforced before any id-driven mutation, so the + object is neither renamed nor deleted regardless of the id supplied. + """ + # Seed an object as admin so there is an existing object with a stable id. + await client.schema.load(schemas=[helper.schema_file("infra_simple_01.json")]) + existing = registry.schema.get(name="TestDevice", branch=default_branch.name) + existing_id = existing.id + assert existing_id + + # A fresh account in no group has no schema-management permission. + unprivileged_token = str(UUIDT()) + await create_account(db=db, name="no_schema_perm", password="testing_password", token_value=unprivileged_token) + + payload = { + "schemas": [ + { + "version": "1.0", + "nodes": [ + { + "id": existing_id, + "name": "DeviceRenamed", + "namespace": "Test", + "attributes": [{"name": "name", "kind": "Text"}], + } + ], + } + ] + } + response = await test_client.post( + "/api/schema/load", + json=payload, + headers={"X-INFRAHUB-KEY": unprivileged_token}, + ) + + assert response.status_code == 403 + assert response.json()["errors"][0]["message"] == "You are not allowed to manage the schema" + + # The targeted object was neither renamed nor deleted. + still_present = registry.schema.get(name="TestDevice", branch=default_branch.name) + assert still_present.id == existing_id + + async def test_write_contract_parity_sdk_offline_vs_load_endpoint( + self, + initial_dataset: str, + test_client: InfrahubTestClient, + api_admin_token: str, + ) -> None: + """The same payload yields the same field/enum verdict offline (SDK) and via POST /api/schema/load. + + A valid payload passes both; an invalid payload is rejected by both, and every field the + SDK names offline appears in the server's rejection response. + """ + valid_schema_root = { + "version": "1.0", + "nodes": [ + { + "name": "Device", + "namespace": "Test", + "attributes": [{"name": "name", "kind": "Text"}], + } + ], + } + invalid_schema_root = { + "version": "1.0", + "nodes": [ + { + "name": "Device", + "namespace": "Test", + "attributes": [ + { + "name": "name", + "kind": "Text", + "inherited": True, + "not_a_real_field": "value", + } + ], + } + ], + } + + # Valid payload: SDK offline verdict is "valid" and the server accepts it. + offline_valid = validate_schema(schema=valid_schema_root) + assert offline_valid.valid is True + response_valid = await test_client.post( + "/api/schema/load", + json={"schemas": [valid_schema_root]}, + headers={"X-INFRAHUB-KEY": api_admin_token}, + ) + assert response_valid.status_code == 200 + + # Invalid payload: SDK offline verdict is "invalid" and the server rejects it (422), + # naming every field the SDK named offline. + offline_invalid = validate_schema(schema=invalid_schema_root) + assert offline_invalid.valid is False + response_invalid = await test_client.post( + "/api/schema/load", + json={"schemas": [invalid_schema_root]}, + headers={"X-INFRAHUB-KEY": api_admin_token}, + ) + assert response_invalid.status_code == 422 + + body = response_invalid.text + offending_fields = {error.field.rsplit(".", 1)[-1] for error in offline_invalid.errors} + assert {"inherited", "not_a_real_field"} <= offending_fields + for field in offending_fields: + assert field in body diff --git a/backend/tests/unit/api/test_schema_load_contract.py b/backend/tests/unit/api/test_schema_load_contract.py new file mode 100644 index 00000000000..595277e60fd --- /dev/null +++ b/backend/tests/unit/api/test_schema_load_contract.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any + +import pytest + +from infrahub.api.schema import SchemaLoadAPI +from infrahub.core.schema import SchemaRoot +from tests.helpers.schema.snow import SNOW_INCIDENT, SNOW_REQUEST, SNOW_TASK + + +def _full_internal_dump() -> dict[str, Any]: + """A full internal SchemaRoot dump, carrying read-only/internal fields (ids, state, ...).""" + return SchemaRoot(version="1.0", generics=[SNOW_TASK], nodes=[SNOW_INCIDENT, SNOW_REQUEST]).model_dump() + + +@dataclass +class LoadContractCase: + name: str + payload: dict[str, Any] = field(default_factory=dict) + accepted: bool = True + use_full_dump: bool = False + + +LOAD_CONTRACT_CASES = [ + LoadContractCase(name="full-internal-dump-tolerated", use_full_dump=True, accepted=True), + LoadContractCase( + name="minimal-write-payload", + payload={ + "version": "1.0", + "nodes": [{"namespace": "Test", "name": "Widget", "attributes": [{"name": "field_one", "kind": "Text"}]}], + }, + accepted=True, + ), + LoadContractCase( + name="forbidden-field-on-extension-rejected", + payload={"version": "1.0", "extensions": {"nodes": [{"kind": "BuiltinTag", "namespace": "Forbidden"}]}}, + accepted=False, + ), + LoadContractCase( + name="unknown-field-on-node-rejected", + payload={"version": "1.0", "nodes": [{"namespace": "Test", "name": "Widget", "not_a_field": 1}]}, + accepted=False, + ), + LoadContractCase( + name="out-of-range-attribute-name-rejected", + payload={ + "version": "1.0", + "nodes": [{"namespace": "Test", "name": "Widget", "attributes": [{"name": "ab", "kind": "Text"}]}], + }, + accepted=False, + ), +] + + +@pytest.mark.parametrize("case", [pytest.param(tc, id=tc.name) for tc in LOAD_CONTRACT_CASES]) +def test_schema_load_contract(case: LoadContractCase) -> None: + payload = _full_internal_dump() if case.use_full_dump else case.payload + if case.accepted: + loaded = SchemaLoadAPI.model_validate(payload) + # the internal schema is built from the projected write payload + assert loaded.internal_schema is not None + else: + with pytest.raises(ValueError, match="validation error for SchemaLoadAPI"): + SchemaLoadAPI.model_validate(payload) diff --git a/backend/tests/unit/core/schema/test_generated_visibility.py b/backend/tests/unit/core/schema/test_generated_visibility.py new file mode 100644 index 00000000000..b728fb50f33 --- /dev/null +++ b/backend/tests/unit/core/schema/test_generated_visibility.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import enum +import types +import typing + +import pytest +from infrahub_sdk.schema.generated import write as sdk_write + +from infrahub.core.constants import Visibility +from infrahub.core.schema.definitions.internal import ( + attribute_schema, + base_node_schema, + generic_schema, + node_schema, + relationship_schema, +) +from infrahub.types import ATTRIBUTE_KIND_LABELS + +# Map each schema-definition family to the generated SDK *write* model it produces. The attribute +# family is a discriminated union on kind; its shared fields live on AttributeSchemaBaseWrite, so +# shared-field checks introspect the base while kind coverage is asserted per variant separately. +DEFINITION_TO_WRITE_MODEL = { + "attribute": (attribute_schema, sdk_write.AttributeSchemaBaseWrite), + "relationship": (relationship_schema, sdk_write.RelationshipSchemaWrite), + "base_node": (base_node_schema, sdk_write.BaseNodeSchemaWrite), + "node": (node_schema, sdk_write.NodeSchemaWrite), + "generic": (generic_schema, sdk_write.GenericSchemaWrite), +} + + +def _allowed_values(annotation: typing.Any) -> tuple[typing.Any, ...] | None: + """Return the bounded set of values a field publishes, unwrapping an optional union. + + A generated field publishes its allowed set either as a ``Literal[...]`` or as a + dedicated ``Enum`` subclass; both are recognised here. Returns None when the + annotation is neither (optionally), i.e. the field is unbounded (bare ``str``/``int``). + """ + origin = typing.get_origin(annotation) + if origin is typing.Literal: + return typing.get_args(annotation) + + # A dedicated Enum subclass publishes its allowed set as its members. + if isinstance(annotation, type) and issubclass(annotation, enum.Enum): + return tuple(annotation) + + # Unwrap ``X | None`` / ``Optional[X]`` and look for a bounded member. + if origin in {typing.Union, types.UnionType}: + for arg in typing.get_args(annotation): + values = _allowed_values(arg) + if values is not None: + return values + return None + + +# Core user-settable fields that must remain writable; a settable field silently defaulting to a +# non-write visibility would drop out of the write model and break loading a hand-authored schema. +REQUIRED_WRITE_FIELDS = { + "node": {"name", "namespace", "description"}, + "attribute": {"name", "kind", "optional", "read_only"}, + "relationship": {"name", "peer", "kind", "cardinality", "optional"}, +} + + +@pytest.mark.parametrize("family", list(DEFINITION_TO_WRITE_MODEL)) +def test_write_model_exposes_no_read_or_internal_field(family: str) -> None: + """The generated write model of each family exposes only write-level fields.""" + definition, write_model = DEFINITION_TO_WRITE_MODEL[family] + + non_write_names = {field.name for field in definition.attributes if field.visibility is not Visibility.WRITE} | { + rel.name for rel in definition.relationships if rel.visibility is not Visibility.WRITE + } + + leaked = non_write_names & set(write_model.model_fields) + assert not leaked, f"{write_model.__name__} exposes non-write fields: {sorted(leaked)}" + + +@pytest.mark.parametrize("family", list(REQUIRED_WRITE_FIELDS)) +def test_write_model_keeps_core_settable_fields(family: str) -> None: + """Each family's core user-settable fields stay present in its write model. + + Guards against a settable field silently defaulting to a non-write visibility and being + dropped from the write model, which would reject a valid hand-authored schema. + """ + _, write_model = DEFINITION_TO_WRITE_MODEL[family] + + missing = REQUIRED_WRITE_FIELDS[family] - set(write_model.model_fields) + assert not missing, f"{write_model.__name__} dropped settable fields: {sorted(missing)}" + + +@pytest.mark.parametrize("family", list(DEFINITION_TO_WRITE_MODEL)) +def test_write_model_publishes_allowed_values(family: str) -> None: + """No write-level constrained field is bare str/int; each publishes its allowed set.""" + definition, write_model = DEFINITION_TO_WRITE_MODEL[family] + + for field in definition.attributes: + if field.visibility is not Visibility.WRITE or not field.enum: + continue + + model_field = write_model.model_fields.get(field.name) + assert model_field is not None, f"{write_model.__name__} is missing constrained field {field.name!r}" + + values = _allowed_values(model_field.annotation) + assert values is not None, ( + f"{write_model.__name__}.{field.name} does not publish its allowed set; a bounded set is defined internally" + ) + assert set(values) == set(field.enum), ( + f"{write_model.__name__}.{field.name} allowed values {sorted(values)} " + f"do not match the internal set {sorted(field.enum)}" + ) + + +def test_attribute_kind_variants_partition_all_kinds() -> None: + """The attribute union's variants together cover every attribute kind, without overlap. + + ``kind`` is the union discriminator, so each kind must resolve to exactly one variant; the + variants' narrowed ``kind`` literals must partition the full internal set of attribute kinds. + """ + union_members = typing.get_args(typing.get_args(sdk_write.AttributeSchemaWrite)[0]) + assert union_members, "AttributeSchemaWrite must be a non-empty discriminated union" + + seen: list[str] = [] + for variant in union_members: + values = _allowed_values(variant.model_fields["kind"].annotation) + assert values is not None, f"{variant.__name__}.kind must be a Literal of its supported kinds" + seen.extend(values) + + assert sorted(seen) == sorted(ATTRIBUTE_KIND_LABELS), ( + f"variant kinds {sorted(seen)} do not cover the internal set {sorted(ATTRIBUTE_KIND_LABELS)}" + ) + assert len(seen) == len(set(seen)), f"attribute kinds overlap across variants: {sorted(seen)}" diff --git a/changelog/+infp-234-schema-load-read-only.fixed.md b/changelog/+infp-234-schema-load-read-only.fixed.md new file mode 100644 index 00000000000..56cf5efc428 --- /dev/null +++ b/changelog/+infp-234-schema-load-read-only.fixed.md @@ -0,0 +1 @@ +The `/api/schema/load` endpoint now tolerates read-only and internal fields on a submitted schema (for example a schema read back from Infrahub, or an exported schema), stripping them before applying the write contract. Fields that are genuinely unknown or invalid for their context are still rejected. diff --git a/changelog/+infp-234.changed.md b/changelog/+infp-234.changed.md new file mode 100644 index 00000000000..d084c472aef --- /dev/null +++ b/changelog/+infp-234.changed.md @@ -0,0 +1,5 @@ +**Breaking:** `POST /api/schema/load` now validates every submitted node and generic against a user-facing *write* contract and rejects any field a user may not set. Payloads that were previously accepted but carried read-only or internal fields — `inherited`, `used_by`, `hierarchy`, and a derived `kind` on nodes/generics — are now rejected with a field-level error naming each offending field. Constrained fields (for example an attribute `kind` or a relationship `cardinality`) set outside their allowed values are rejected the same way. + +`GET /api/schema` is unchanged and still returns read-only fields such as `inherited` and `used_by`. + +To produce a submittable payload, strip the non-settable fields before loading. The write contract is published as a committed model in the Python SDK (`infrahub_sdk.schema.generated.write`); the SDK `validate_schema()` helper reproduces the server verdict offline so a payload can be checked before submission. See the migration guide "Stricter schema-load validation" in the release notes for the full field list and client-side steps. diff --git a/dev/knowledge/backend/schema-definitions.md b/dev/knowledge/backend/schema-definitions.md index 3180dd6fd65..586f7b71a27 100644 --- a/dev/knowledge/backend/schema-definitions.md +++ b/dev/knowledge/backend/schema-definitions.md @@ -85,6 +85,52 @@ All `AttributeSchema` entries must include a `description` field. This is enforc `backend/tests/component/message_bus/operations/requests/test_proposed_change.py::test_get_proposed_change_schema_integrity_constraints` contains hardcoded constraint counts. These counts change whenever schemas are added or removed because `ConstraintValidatorDeterminer` iterates all schemas in the registry and generates one `SchemaUpdateConstraintInfo` per validatable property. After schema changes, run the test to get actual counts and update the assertions. See `#2592` for planned improvements. +## Field Visibility and the Write / Read / Internal Models + +Every schema field carries a `visibility` classification in its `extra` metadata, defined by +the `Visibility` enum in `backend/infrahub/core/constants/schema.py`. The three levels are +ordinal and nested — `write ⊆ read ⊆ internal`: + +| Level | Who may see/set it | Examples | +|-------|--------------------|----------| +| `WRITE` | User may submit it on load | `name`, `namespace`, `attributes`, `relationships` | +| `READ` | Returned on `GET /api/schema` but not settable | `inherited`, `used_by`, `hierarchy`, derived `kind` | +| `INTERNAL` | Backend-only, never exposed | internal bookkeeping fields | + +Fields default to `INTERNAL` unless their definition sets a higher `visibility`, so a new +field is hidden until it is deliberately classified. + +### Model families + +The `internal.py` definitions remain the single source of truth. `invoke backend.generate` +renders two model families from them by filtering each field on its visibility level (see +`_generate_schemas_sdk` in `tasks/backend.py`): + +- **write models** — include only `WRITE` fields and set `extra="forbid"`, so submitting a + read-only, internal, or unknown field is rejected. +- **read models** — include `WRITE` and `READ` fields, describing the shape returned by + `GET /api/schema`. + +Because both families are generated from the same definitions, a field's classification is +declared once and both the write contract and the read shape follow automatically. + +### Backend → SDK dependency + +The generated write/read models are rendered **into the Python SDK**, at +`python_sdk/infrahub_sdk/schema/generated/{write,read}.py`. The output is self-contained +(only `pydantic` + `typing`) so it imports with just the SDK installed — no backend, no +server. This inverts the usual direction: the backend's schema definitions are the source, +and the generator writes the artifact into the SDK submodule, where it is committed and +shipped inside the published package. + +`POST /api/schema/load` enforces the write contract at the boundary by calling the SDK's +`validate_schema()` (`python_sdk/infrahub_sdk/schema/validate.py`) from the +`validate_write_contract` validator on `SchemaLoadAPI` (`backend/infrahub/api/schema.py`). +The same validator runs offline in the SDK, so a client gets the identical field-level +verdict before submitting. After changing a field's `visibility` (or adding a field), run +`invoke backend.generate` and commit the regenerated SDK models alongside the backend +change; CI fails if the generated artifact is stale. + ## Key Locations | Component | Path | @@ -92,6 +138,10 @@ All `AttributeSchema` entries must include a `description` field. This is enforc | Core schema definitions | `backend/infrahub/core/schema/definitions/core/` | | Internal schema definitions | `backend/infrahub/core/schema/definitions/internal/` | | Generated schemas (do not edit) | `backend/infrahub/core/schema/generated/` | +| `Visibility` enum | `backend/infrahub/core/constants/schema.py` | +| SDK write/read generator | `_generate_schemas_sdk` in `tasks/backend.py` | +| Generated SDK write/read models (do not edit) | `python_sdk/infrahub_sdk/schema/generated/{write,read}.py` | +| Offline write-contract validator | `python_sdk/infrahub_sdk/schema/validate.py` | | RelationshipSchema class | `backend/infrahub/core/schema/relationship_schema.py` | | AttributeSchema class | `backend/infrahub/core/schema/attribute_schema.py` | | GenericSchema class | `backend/infrahub/core/schema/generic_schema.py` | diff --git a/dev/specs/002-user-facing-schema/PRD-user-facing-schema-separation.md b/dev/specs/002-user-facing-schema/PRD-user-facing-schema-separation.md new file mode 100644 index 00000000000..fc145289654 --- /dev/null +++ b/dev/specs/002-user-facing-schema/PRD-user-facing-schema-separation.md @@ -0,0 +1,126 @@ +# PRD: User-Facing Schema Separation (INFP-234) + +## Problem Statement + +The schema that users author against (the "user-facing schema") is generated as a partial copy of Infrahub's internal schema, and it is wrong in two ways. It exposes fields users are not meant to set (e.g. `inherited`), so users set them and create problems in Infrahub. And it under-specifies fields whose valid values are already known internally — attribute `kind`, for example, is published as a bare string with no enumeration of valid kinds. Humans produce technically-invalid schemas as a result, and an LLM generating a schema has no way to know which fields exist or what values they accept, so it cannot reliably produce a correct schema at all. + +## Solution Overview + +Generate the user-facing schema as a distinct contract instead of dumping the internal model. From the single internal source of truth, Infrahub produces three schemas: an external **write** schema (exactly what a user may submit), an external **read** schema (adds fields the user may see but not set), and the **internal** schema. Every field is classified by visibility; fields whose allowed values are already known internally publish those values as enumerations. The write and read models live in the Python SDK, so a schema can be validated locally without a running server, and the server validates with the same models. Submitting a field a user may not set is rejected with a clear, field-level error rather than silently accepted. + +## User Stories + +1. As an LLM agent, I want the write schema to list exactly which fields exist and which values each constrained field accepts, so that I can generate a valid schema without guessing. +2. As an LLM agent, I want an invalid submission rejected with a machine-readable, field-level error, so that I can correct my output automatically instead of loading a broken schema. +3. As a human schema author, I want internal-only fields to stop being advertised as settable, so that I don't set fields that break Infrahub. +4. As a human schema author, I want a clear error naming the offending field when I submit something invalid, so that I can fix it quickly. +5. As an SDK user, I want to validate a schema locally with no server, so that I can catch errors in CI or offline before a round-trip. +6. As an SDK user, I want the local verdict to match the server's for field and enum rules, so that a locally-valid schema is not rejected on load. +7. As a maintainer, I want the write/read/internal models generated from one source, so that they cannot drift apart. +8. As a maintainer, I want a new field hidden from users by default, so that we never leak an internal field by forgetting to classify it. + +## User Journeys (prioritised) + +### P1 — Agent authors a valid schema unassisted +- Journey: An LLM agent fetches the published write schema, generates a payload, and submits it to `/api/schema/load`. +- Acceptance: **Given** an agent has the published write schema, **When** it generates a payload and POSTs it, **Then** either the payload validates and loads because the schema stated exactly which fields exist and each constrained field's allowed values, or it is rejected with a field-level machine-readable error — never silently accepting an invalid schema. + +### P2 — Offline schema validation (ships independently) +- Journey: A developer or agent validates a schema against the SDK's write model with no running server. +- Acceptance: **Given** only the Infrahub SDK is installed and no server is running, **When** a schema is validated against the SDK write model, **Then** unknown / read-only / internal fields and out-of-enum values are caught locally, and the same models re-validate server-side so a locally-valid schema is not rejected for field/enum reasons on load. + +## Functional Requirements + +- **FR-001**: `backend.generate` MUST emit three model sets (write, read, internal) from the single field-definition source. *Verify:* generated output contains three model families; regeneration is idempotent. +- **FR-002**: Every schema field MUST carry a `visibility` classification (`internal` / `read` / `write`); an unset marker MUST default to `internal`. *Verify:* unit test on the default and on per-model field membership. +- **FR-003**: The write model MUST reject any field not at `write` level (read, internal, or unknown) with a field-level error naming the field. *Verify:* POST containing `inherited` plus a bogus field returns a 4xx naming each. +- **FR-004**: Any field whose internal definition carries an `enum=` or equivalent bounded type MUST surface that enumeration in the write and read models, across the attribute, relationship, node, and generic families. *Verify:* generated write schema for `kind` contains the full set of valid kinds; a scan asserts no bare-string field exists where an internal enum is defined. +- **FR-005**: The read model MUST include `read`-level fields and exclude `internal` fields. *Verify:* GET returns `inherited`; it never returns internal bookkeeping fields. +- **FR-006**: The write and read models MUST be generated into the Python SDK and be importable with only the SDK installed (no server, no backend package). *Verify:* import and validate a payload in an SDK-only environment. +- **FR-007**: The backend MUST validate write submissions and serialise read responses using the SDK-hosted models, not a backend-local copy. *Verify:* a rule change in the generated model changes both server and SDK behaviour. +- **FR-008**: The SDK's existing hand-written schema models MUST be replaced by the generated write/read models, leaving no third parallel definition. *Verify:* old model classes are removed; SDK callers use the generated ones. + +## Key Entities + +- **AttributeSchema / RelationshipSchema / NodeSchema / GenericSchema** *(existing)*: the field definitions that gain a `visibility` classification; source of the generated models. +- **`visibility` field marker** *(new)*: ordinal metadata on each field definition; determines which of the three models a field appears in. Governance-relevant: it is the control that decides what users can touch. +- **Write / Read generated models** *(new)*: the externally-visible contract, hosted in the Python SDK, consumed by both the SDK and the backend API. +- **Internal generated model** *(existing)*: unchanged full model, backend-only. + +## Edge Cases + +- API round-trip (`GET /api/schema` → edit → `POST /api/schema/load`) breaks this cycle because read-level fields are now rejected on write; the write-shaped export that fixes this is deferred, and the minority of clients doing this must strip read fields against the published write schema in the meantime. +- The `kind` field that `APISchemaMixin` injects from `namespace` + `name` must remain compatible with the write model. +- Schema `extensions` payloads must be subject to the same write-model rejection rules as node/generic definitions. +- Reading back a previously-stored schema that contains fields now classified `read` must still succeed (read is a superset of write, so this is safe). +- Version skew between SDK-side validation and the server's schema version: the `version` field on the load payload is the compatibility anchor; behaviour on mismatch is defined at spec time. + +## Success Criteria + +- **SC-001**: The write schema exposes zero internal-only fields — every field present is user-settable. +- **SC-002**: Zero constrained fields publish a bare type where the allowed value set is already known internally (100% enum propagation). +- **SC-003**: 100% of submissions containing a non-writable field are rejected with a field-level error; zero are silently accepted. +- **SC-004**: An agent given only the write schema loads a valid schema for a benchmark task set at or above a target pass rate, with no human correcting field names or kinds. *(Target rate set with product.)* +- **SC-005**: A schema can be validated with only the SDK installed and no server process, and the local verdict matches the server's for field and enum rules. + +## Implementation Decisions + +- Modules to build / modify: + - `visibility` field marker (backend schema definitions, extends): adds the ordinal visibility axis to field definitions. + - Schema model generator (backend generate pipeline, extends — deep module): emits three model sets from one source, filters by visibility, propagates known enums/constraints, and writes the write/read models into the SDK. + - Generated write/read models (Python SDK, new): the externally-visible contract, importable without a server. + - SDK schema validation surface (Python SDK, extends/replaces): replaces the hand-written SDK schema models and exposes offline validation. + - API schema-load validation (backend API, extends): validates against the SDK write model with hard rejection and field-level errors; serialises GET via the read model. +- API / interface surface: `/api/schema/load` becomes stricter (breaking for payloads carrying non-write fields); `/api/schema` read shape changes; new SDK local-validation entry point. The write-shaped export endpoint is out of scope this cycle. +- Error handling: field-level, machine-readable rejection errors that name the offending field and why it is not accepted. +- Data / persistence: none — this is an API-model and generation-layer change, no stored-data model change or migration. +- Frontend surface: none in this cycle; the `schema-visualizer` package is a separate downstream consumer assessed later. +- SDK / CLI surface: the SDK gains the generated write/read models and a local-validation capability; hand-written schema models are removed. + +## Testing Decisions + +- **What makes a good test here.** Test the externally-observable contract — which fields each model accepts/emits, that constrained fields carry their enums, and that invalid submissions are rejected with the right error — not the internals of the generator. +- **Unit tests** (agreed): schema model generator (idempotency, per-model field membership, enum propagation); API rejection behaviour (field-level errors); SDK offline validation (SDK-only import and verdict parity with the server). +- **Integration / contract tests**: server/SDK parity — the same payload yields the same field/enum verdict locally and on `/api/schema/load`; a load with an internal/read field is rejected end-to-end. +- **E2E scenario**: an agent-style flow fetches the write schema, generates a payload, loads it successfully; a second payload carrying `inherited` is rejected with a field-level error. +- **Prior art**: existing schema-load API tests and the schema-generation tests under the backend test suite. + +## Constitution Alignment + +- **III — Type Safety & Explicit Contracts**: directly advances it; the feature defines the REST/SDK contract explicitly and makes consumers use generated types. +- **VII — Simplicity & Maintainability**: one generated source produces three outputs and deduplicates the SDK's schema models rather than adding a parallel set. +- **VI — Security & Input Boundaries**: hard rejection enforces validation at the API boundary instead of silently accepting invalid input. +- **I — Schema-Driven Integrity**: fewer invalid schemas enter the system, protecting downstream data. + +## Governance Gates Crossed + +- [x] **GraphQL schema modifications** — not expected (schema load is REST); confirm at spec time. +- [x] **API / public interface change** — `/api/schema/load` and `/api/schema` change shape/behaviour; breaking for non-write payloads. Ask-first. +- [ ] **Database schema or migration change** — none. +- [ ] **New dependency** — none. +- [x] **CI/CD workflow change** — the generate pipeline now writes into the `python_sdk` submodule; confirm CI handles the submodule write and that generated files are validated on both sides. +- [ ] **Authentication / authorization change** — none. +- [x] **Generated files + submodule commits** — new/changed generated models in backend and `python_sdk`; regenerate and commit (`backend.generate`, `schema.generate-jsonschema`, `docs.generate`) with explicit submodule commits. + +## Assumptions + +- The visibility hierarchy is nested (`write ⊆ read ⊆ internal`); there are no write-only fields (settable but not returned). +- The dominant authoring path is user-authored, write-shaped YAML loaded into Infrahub; API read-modify-write round-tripping is a minority path. +- Internal `enum=` definitions are authoritative and complete at generation time. +- The backend already depends on the SDK at runtime, and the generate pipeline can target the SDK submodule. + +## Out of Scope + +- The write-shaped export capability (export excluding read-only fields, emitting only user-defined values with defaults omitted) — deferred; needs value provenance. +- Kind-conditional `read_only` defaults for `computed_attribute` / `NumberPool` attributes — a defaults-and-conditional-validation problem, tracked separately. +- Changes to the `frontend/packages/schema-visualizer` consumer — assessed after the models land. + +## Open Questions + +- [NEEDS CLARIFICATION: the concrete per-field classification — every field in the attribute/relationship/node/generic definitions must be assigned write/read/internal. Mechanical; resolved at spec time.] +- [NEEDS CLARIFICATION: the SC-004 target pass rate and benchmark task set — needs product input.] + +## Further Notes + +- Related ADRs: none directly; honours `dev/adr/` decisions in the schema area. +- Source of this PRD: a grilling session on Jira idea INFP-234, grounded in the current schema-generation code (single internal source, existing `extra={}` field-metadata channel, `ATTRIBUTE_KIND_LABELS` enum dropped during generation). diff --git a/dev/specs/002-user-facing-schema/alignment-check.md b/dev/specs/002-user-facing-schema/alignment-check.md new file mode 100644 index 00000000000..890dee024bd --- /dev/null +++ b/dev/specs/002-user-facing-schema/alignment-check.md @@ -0,0 +1,41 @@ +# Spec/Ask Alignment Check: User-Facing Schema Separation + +**Date**: 2026-07-01 | **Feature**: `specs/002-user-facing-schema` + +## Source + +Source-of-truth PRD (local files at repo root, no URLs): +- `PRD-user-facing-schema-separation.md` +- `schema-field-classification.md` (resolved per-field write/read/internal mapping) + +Compared against `specs/002-user-facing-schema/spec.md`. + +## Verdict + +⚠️ **MINOR DRIFT (proceeding)** — every PRD requirement, success criterion, journey, +out-of-scope item, and governance gate is faithfully represented. The spec *adds* a +handful of requirements, all of which are justified clarifications or +constitution-mandated governance (not scope creep, and nothing dropped, softened, or +contradicted). Safe to proceed to implementation planning review. + +## Findings + +| Severity | Category | PRD reference | Spec reference | Description | +|----------|----------|---------------|----------------|-------------| +| ✅ none | mapping | FR-001..FR-008 | FR-001, FR-002, FR-003, FR-004, FR-006, FR-007, FR-008, FR-009 | All eight PRD functional requirements are present (renumbered; PRD FR-005 → spec FR-006). | +| info | added (justified) | — | FR-005 (out-of-enum rejection) | Necessary clarification derived from PRD SC-002 (enum completeness) — makes the enum contract testable at the boundary. | +| info | added (justified) | Edge case "reading back a previously-stored schema" | FR-010 | Promotes a PRD edge case to a testable requirement. | +| info | added (governance) | Governance: "API change… ask first" + constitution changelog gate | FR-011 (changelog + upgrade note) | Added by critique M1; constitution mandates a changelog for user-facing changes and this is a breaking change. Justified, not scope creep. | +| info | added (justified) | FR-006 ("importable with only the SDK installed") | FR-012 (SDK models committed/shipped) | Added by critique M2; FR-006 is only true if the generated models ship in the SDK package. Makes an existing PRD requirement actually satisfiable. | +| info | added (justified) | PRD SC-001..005 | SC-006 (no regression) | Expansion guarding backward compatibility (PRD edge cases imply it). | +| info | changed (faithful) | PRD user stories #3/#4 (human author) | US3 (P2 journey) | The human-author value in the PRD user-story list is promoted to a P2 journey; content preserved. | +| info | added (justified) | Governance (API change) + critique R1/R2 | "Dependencies & Governance" notes on server/SDK release compat and `id`-driven-mutation authz | Operational/security clarifications from the critique; consistent with PRD scope. | +| ✅ none | out of scope | Export endpoint; kind-conditional read_only defaults; schema-visualizer | Out of Scope | All three PRD non-goals carried verbatim. | +| ✅ none | field mapping | schema-field-classification.md | "Field visibility model" + data-model.md | The resolved ⚠ decisions (state=write, hierarchy/hierarchical=read, identifier=write, id=write, node back-ref=internal) match the classification doc. | +| 🤔 open | success metric | PRD open question (SC-004 target) | SC-004 + Open Questions | Carried forward unchanged; product input pending. Non-blocking. | + +## Action + +**Proceed.** No missing PRD requirements, none dropped/softened/contradicted. The +additions are justified clarifications and constitution-mandated governance. No +remediation pass required (remediation counter: 0). diff --git a/dev/specs/002-user-facing-schema/checklists/requirements.md b/dev/specs/002-user-facing-schema/checklists/requirements.md new file mode 100644 index 00000000000..23460a0b584 --- /dev/null +++ b/dev/specs/002-user-facing-schema/checklists/requirements.md @@ -0,0 +1,41 @@ +# Specification Quality Checklist: User-Facing Schema Separation + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-07-01 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain *(one remains: SC-004 target rate — product input, non-blocking for planning)* +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- One `[NEEDS CLARIFICATION]` remains (SC-004 benchmark target rate). It requires + product input on a numeric target and does not block planning — the criterion, + its verification method, and the benchmark concept are all defined; only the + threshold number is open. Within the 3-marker limit. +- The spec necessarily uses domain terms (schema, node, generic, attribute, + relationship, submission/read-back) — these are the project's own vocabulary, not + implementation choices. Endpoint paths and language/framework names were kept out + of the requirements and success criteria. diff --git a/dev/specs/002-user-facing-schema/contracts/api-and-sdk.md b/dev/specs/002-user-facing-schema/contracts/api-and-sdk.md new file mode 100644 index 00000000000..1f8b37ce508 --- /dev/null +++ b/dev/specs/002-user-facing-schema/contracts/api-and-sdk.md @@ -0,0 +1,53 @@ +# Contract: API Behaviour & SDK Offline Validation + +## POST /api/schema/load — submission + +- Validates the payload against the **write** model (SDK-hosted). +- **Rejects** any field not at `write` level: + - A `read`/`internal`/unknown field triggers rejection (via the write model's + `extra="forbid"`), with a field-level, machine-readable error naming the field. + - Where feasible, the message distinguishes "field is read-only / not settable" + from "unknown field" for clarity. +- **Rejects** a `write`-level constrained field set outside its allowed set, naming + the field and the invalid value. +- On rejection, **nothing is stored** (atomic — existing behaviour). +- The `kind`-from-`namespace`+`name` derivation continues to work under the write model. +- Schema `extensions` payloads are subject to the same rejection rules. + +**Verification**: +- POST with `inherited: true` + an unknown field → 4xx naming both. +- POST with a non-existent attribute `kind` → 4xx naming field + value. +- POST of a valid write-shaped schema → loads successfully (idempotent). +- POST that extends an existing node with a non-write field → rejected. + +## GET /api/schema — read-back + +- Serialises using the **read** model (SDK-hosted). +- Returns `read`-level fields (e.g. `inherited`, `used_by`); never returns + `internal` fields (parent back-reference) or unclassified fields. +- A schema stored before this change (possibly containing now-`read` fields) reads + back without error. + +**Verification**: +- GET returns `inherited`/`used_by`; response contains no internal-only field. +- Reading a pre-existing stored schema succeeds. + +## SDK offline validation + +- With only the SDK installed (no server, no backend package), a caller can validate + a schema payload against the write model and get a pass/fail verdict that names the + offending field(s) on failure. +- The verdict matches the server's for all field-presence and allowed-value rules. + +**Verification**: +- In an SDK-only environment: a valid payload passes; a payload with a non-settable + field or out-of-range value fails, naming the field. +- Parity test: the same payload yields the same field/enum verdict locally and via + `POST /api/schema/load`. + +## Backward-compatibility notes + +- API read-modify-write round-trips break this cycle (read fields rejected on load); + clients strip non-write fields against the published write schema. The write-shaped + export that removes this friction is deferred (out of scope). +- Previously loadable write-shaped schemas continue to load unchanged. diff --git a/dev/specs/002-user-facing-schema/contracts/schema-models.md b/dev/specs/002-user-facing-schema/contracts/schema-models.md new file mode 100644 index 00000000000..91948ff8839 --- /dev/null +++ b/dev/specs/002-user-facing-schema/contracts/schema-models.md @@ -0,0 +1,61 @@ +# Contract: Generated Schema Models (write / read / internal) + +Defines the shape and generation rules for the three model families. Consumers: +the backend API layer, the SDK, and any agent reading the published JSON-schema. + +## Generation inputs → outputs + +- **Input**: `internal.py` definitions, each field carrying `extra={"update": ..., "visibility": ...}`. +- **Process**: `uv run invoke backend.generate` renders, per family, three variants + by filtering fields on `visibility` (see membership rule in data-model.md). +- **Outputs**: + - Internal variant → `backend/infrahub/core/schema/generated/*.py` (unchanged location). + - Write + Read variants → `python_sdk/infrahub_sdk/schema/` (generated; replace hand-written). +- **Guarantee**: regeneration is byte-stable (idempotent). CI validates no drift on both sides. +- **Shipping**: the generated SDK write/read models are committed, version-controlled + artifacts included in the published SDK package (not build-time-only), so a consumer + installing only the SDK obtains them (basis for the "importable with only the SDK + installed" contract). + +## Write model contract + +- Contains **only** `write`-level fields for each family. +- `model_config` retains `extra="forbid"`. +- Constrained fields publish their allowed-value set (enum / `Literal`) so the + emitted JSON-schema is complete. +- Importable with only the SDK installed (no backend, no server). +- Is the exact acceptance shape for `POST /api/schema/load`. + +**Verification**: +- The write model for a node exposes no `read`/`internal` field (e.g. no `inherited`, + no `used_by`, no parent back-reference). +- The attribute `kind` field's JSON-schema lists all of `ATTRIBUTE_KIND_LABELS`. +- A scan finds zero write-model fields typed as bare `str`/`int` where the source + definition declares an `enum=`/enum `internal_kind`. + +## Read model contract + +- Contains `write` + `read` fields; excludes `internal`. +- Superset of the write model (every write field is present and identically typed). +- Injects `kind` from `namespace` + `name` (existing `APISchemaMixin` behaviour) for + node/generic; carries any read-only response fields the GET endpoint returns today + (e.g. `hash`) as read-level additions. +- Is the exact shape returned by `GET /api/schema`. + +**Verification**: +- The read model includes `inherited`/`used_by`; excludes the parent back-reference. +- Every field of the write model is present in the read model with the same type. + +## Internal model contract + +- Unchanged field set and location; rich wrapper classes continue to extend it. +- Remains backend-only; not exported to the SDK. + +**Verification**: existing internal-model tests continue to pass unchanged. + +## Cross-variant invariants + +- `write ⊆ read ⊆ internal` field-set nesting holds for every family. +- A field with no `visibility` is absent from write and read (internal-only). +- The base/derived inheritance (`base_node_schema` → node/generic) is preserved in + each variant. diff --git a/dev/specs/002-user-facing-schema/critiques/critique-20260701-120657.md b/dev/specs/002-user-facing-schema/critiques/critique-20260701-120657.md new file mode 100644 index 00000000000..b2f15d289e2 --- /dev/null +++ b/dev/specs/002-user-facing-schema/critiques/critique-20260701-120657.md @@ -0,0 +1,89 @@ +# Critique: User-Facing Schema Separation (INFP-234) + +**Date**: 2026-07-01 | **Feature**: `specs/002-user-facing-schema` +**Inputs reviewed**: spec.md, plan.md, research.md, data-model.md, contracts/, constitution.md + +## Executive Summary + +The spec and plan are strong: the problem is well-evidenced, scope is aggressively +and sensibly carved, the design reuses existing machinery (single-source generation, +already-enforced `extra="forbid"`, the `protocols.py`-into-SDK precedent), and the +constitution's explicit-contract principle is directly advanced. Two **Must-Address** +gaps must be closed before tasks — both are governance/operational, not design flaws: +a required changelog + upgrade note for the breaking API change, and an explicit +statement that the generated SDK models are committed/shipped artifacts (FR-006 is +false otherwise). Verdict: **⚠️ PROCEED WITH UPDATES**. + +## Product Lens Findings + +**3a. Problem validation** — Clear and evidenced (Jira INFP-234, Slack priority bump, +PR comments on read-only kinds). Scope well-bounded. No issue. + +**3b. User value** — Each story (P1 agent authoring, P2 offline validation, P3 human +friction) delivers standalone value; acceptance is outcome-framed. MVP is P1's +write-model + rejection; enum propagation is correctly inside the MVP (P1 fails +without it). No issue. + +**3c. Alternatives** — research.md weighs runtime filtering and hand-written models +and rejects both with reasons. Good. + +**3d. Edge cases & UX** — Round-trip breakage, derived `kind`, extensions, historical +read-back, and version skew are all covered. **Gap**: the breaking change to a public +endpoint has no user-communication/migration deliverable (see M1). + +**3e. Success measurement** — SC-001..003, 005, 006 are measurable. SC-004 lacks a +target (tracked open question, non-blocking). No new issue. + +## Engineering Lens Findings + +**4a. Architecture** — Sound. The one novel risk is the backend→SDK inversion +(backend importing SDK-generated models). Build-ordering and drift are handled +(dev-time generation, CI drift check, protocols precedent). **But** FR-006 +("importable with only the SDK installed") only holds if the generated models are +committed into the SDK and shipped in its package — not build-time-only (see M2). + +**4b. Failure modes** — Schema load is low-frequency and already atomic on rejection. +No new failure surface. OK. + +**4c. Security & privacy** — Hard rejection improves posture. **Watch item**: `id` is +now `write`-level and drives rename/delete of existing objects; confirm existing +authorization prevents targeting arbitrary objects and respects branch scoping (R1). + +**4d. Performance** — Not a hot path; no concern. + +**4e. Testing** — Comprehensive (generator unit, load/read functional, SDK offline, +parity contract). The SC-002 "no bare-string-where-enum-exists" scan should be an +explicit automated test, not a manual check. Otherwise good. + +**4f. Operational readiness** — Rollback = revert generated models + SDK release. +**Gap**: server and SDK now ship the same contract and must be released compatibly; +define the version-compatibility expectation and skew behaviour (R2). + +**4g. Dependencies & integration** — The editable path dep + submodule coupling is +real; the backend now cannot run without the generated SDK models present. Acceptable +given it's already an editable dependency, but must be stated (folds into M2). + +## Cross-Lens Insights + +- **X1 (Both) 🎯** — Breaking `/api/schema/load` behaviour intersects product + (existing pipelines/users break) and engineering (server/SDK must be released in + lockstep). Resolving M1 (changelog + upgrade note) and R2 (version compatibility) + together closes both sides. + +## Findings Summary Table + +| ID | Lens | Severity | Category | Finding | Suggestion | +|----|------|----------|----------|---------|------------| +| M1 | Both | 🎯 | Governance / UX | Breaking public-API change lacks the constitution-mandated changelog fragment and an upgrade/migration note | Add a requirement + deliverable: Towncrier changelog fragment and an upgrade note documenting the stricter load behaviour and how clients strip read-only fields against the published write schema | +| M2 | Engineering | 🎯 | Architecture / Packaging | FR-006 ("importable with only the SDK installed") is false unless generated write/read models are committed into the SDK and shipped in its published package | State explicitly in plan/data-model/contract that generated SDK models are committed, version-controlled artifacts validated by the SDK's own CI (not build-time-only / not gitignored) | +| R1 | Engineering | 💡 | Security | `id` is write-level and drives rename/delete; possible IDOR / cross-branch targeting | Add a test confirming existing authorization + branch scoping govern id-driven mutations; note in spec | +| R2 | Engineering | 💡 | Ops / Versioning | Server & SDK now ship one contract; skew behaviour undefined | Document version-compatibility expectation; `version` field is the anchor; local SDK validation is advisory, server authoritative | +| R3 | Engineering | 💡 | Dev experience | Backend now needs the SDK submodule checked out + generated to run | Bootstrap note (partly in quickstart prereqs); keep `git submodule update --init` in setup | +| Q1 | Product | 🤔 | Success metric | SC-004 target first-attempt rate unset | Product to set the benchmark target + task set | + +## Verdict + +⚠️ **PROCEED WITH UPDATES** — two Must-Address items (M1, M2), both resolvable by +spec/plan edits. Per the autonomous prep run, M1 and M2 are applied now, plus R1/R2 +folded in as spec notes; R3 already reflected in quickstart; Q1 remains the tracked +open question. diff --git a/dev/specs/002-user-facing-schema/data-model.md b/dev/specs/002-user-facing-schema/data-model.md new file mode 100644 index 00000000000..73bdb082b19 --- /dev/null +++ b/dev/specs/002-user-facing-schema/data-model.md @@ -0,0 +1,90 @@ +# Data Model: User-Facing Schema Separation + +No database entities change. The "data model" here is the shape of the generated +schema models and the classification metadata that drives them. + +## Visibility classification (new metadata) + +- Added to `ExtraField` (the `extra={}` TypedDict in `internal.py`) as a `visibility` + key alongside the existing `update` key. +- Value is an ordinal: `internal < read < write`. +- Default when unset: **internal** (a new field is hidden until deliberately promoted). +- Orthogonal to `update:` (post-create mutability). The two are independent axes. +- Authoritative per-field assignment: `schema-field-classification.md` (repo root). + +### Membership rule + +For a field classified at level `L`, it appears in a generated model of variant `V` +iff `V ⊇ L` under the ordering `write ⊆ read ⊆ internal`: + +| Field level | write model | read model | internal model | +|-------------|:-----------:|:----------:|:--------------:| +| `write` | ✅ | ✅ | ✅ | +| `read` | ❌ | ✅ | ✅ | +| `internal` | ❌ | ❌ | ✅ | + +## Model families (generated) + +Each of the four schema families is generated in three variants: + +- **Node** — `node_schema` (+ shared `base_node_schema`) +- **Generic** — `generic_schema` (+ shared `base_node_schema`) +- **Attribute** — `attribute_schema` +- **Relationship** — `relationship_schema` + +Variants: + +- **Internal** *(existing)* — full field set; location unchanged + (`backend/infrahub/core/schema/generated/`). Rich wrapper classes in + `backend/infrahub/core/schema/*_schema.py` continue to extend these. +- **Write** *(new)* — only `write`-level fields; `extra="forbid"` retained so + non-write fields are rejected. Generated into `python_sdk`. +- **Read** *(new)* — `write` + `read` fields; excludes `internal`. Generated into + `python_sdk`. Carries the `kind`-injection behaviour (from `namespace`+`name`). + +The base/derived split (`base_node_schema` → node/generic via `without_duplicates`) +is preserved per variant. + +## Enum / allowed-value propagation + +Fields carrying a known allowed-value set MUST publish it in the write/read models +(they are currently dropped to bare types). Sources: + +| Field(s) | Family | Allowed-value source | +|----------|--------|----------------------| +| `kind` | attribute | `ATTRIBUTE_KIND_LABELS` (list) → `Literal[...]` / json_schema enum | +| `kind` | relationship | `RelationshipKind` (enum) | +| `cardinality` | relationship | `RelationshipCardinality` (enum) | +| `direction` | relationship | `RelationshipDirection` (enum) | +| `on_delete` | relationship | `RelationshipDeleteBehavior` (enum) | +| `branch` | base/attribute/relationship | `BranchSupportType` (enum) | +| `allow_override` | attribute/relationship | `AllowOverrideType` (enum) | +| `display` | attribute/relationship | `SchemaAttributeDisplay` (enum) | +| `state` | all | `HashableModelState` (enum) | + +Enum-class-backed fields are typed as the enum; list-backed sets render a +`Literal[...]` (or `json_schema_extra={"enum": [...]}`) so the emitted JSON-schema +carries the values. + +## Resolved field classification (summary) + +Full table in `schema-field-classification.md`. Key resolved decisions: + +- **write**: all core authoring fields; `read_only`, `identifier` (relationship), + `state` (load directive), deprecated fields (`default_filter`, `display_labels`, + attribute `regex`/`min_length`/`max_length`), and object `id` (used on existing + objects to drive rename/delete). +- **read** (visible, not settable): `inherited` (attribute + relationship), + `used_by` (generic), node `hierarchy`, relationship `hierarchical`. +- **internal** (never exposed): the parent back-reference from an attribute or + relationship to its owning node. + +## Invariants + +- `write ⊆ read ⊆ internal` (no write-only field). +- Regeneration is idempotent (byte-stable output). +- The SDK write/read models import with no backend/server dependency. +- The generated SDK write/read models are committed, version-controlled artifacts + shipped in the SDK package (not build-time-only); the SDK's own CI validates they + are present and non-stale. +- Server and SDK validate against the *same* generated models. diff --git a/dev/specs/002-user-facing-schema/opsmill-implement-followups.md b/dev/specs/002-user-facing-schema/opsmill-implement-followups.md new file mode 100644 index 00000000000..c92a40b2a3f --- /dev/null +++ b/dev/specs/002-user-facing-schema/opsmill-implement-followups.md @@ -0,0 +1,74 @@ +# Follow-ups: user-facing schema separation + +## Follow-up: publish the write contract in the REST OpenAPI + +### Status + +Deferred (documented, not implemented). Attempting it inside the review-fix pass +was judged too risky for the value: it changes a broadly-consumed published +artifact (`schema/openapi.json`) and risks the downstream internal parsing path. + +### Problem + +The load endpoint (`POST /api/schema/load`) enforces the user-facing **write** +contract at runtime via a Pydantic `model_validator(mode="before")` on +`SchemaLoadAPI` (`backend/infrahub/api/schema.py`), delegating to the SDK's +`validate_schema`. That gate rejects non-settable fields and out-of-enum values +with field-level messages. + +However, the gate is invisible to FastAPI's OpenAPI generation. The request body +schema is still generated from the internal models +(`SchemaLoadAPI` → `SchemaRoot` → `NodeSchema` / `AttributeSchema-Input` / +`RelationshipSchema-Input`). As a result the published contract in +`schema/openapi.json`: + +- shows `kind` as a bare `{"type": "string"}` with no `enum` (allowed values not + enumerated), and +- includes non-settable fields (`id`, `state`, `inherited`, and other + read-level/internal fields) in the request body. + +Regenerating the OpenAPI schema alone (`uv run invoke schema.generate-jsonschema`) +does not fix this, because the generator reads the declared request model, not the +before-validator. + +### Why it was not fixed here + +Two viable approaches, both carrying more risk than a review-fix pass should take: + +1. **Declare the endpoint request model as the generated write models and convert + to the internal models downstream.** This is the clean, single-source-of-truth + fix: FastAPI would then generate the correct published contract for free. But it + changes the runtime request type and forces a conversion step into the internal + `SchemaRoot`/`NodeSchema` models that the rest of the load pipeline consumes. + A full swap of the request model was deliberately avoided during implementation + as risky, precisely because downstream code parses into the internal models. + +2. **Inject a custom OpenAPI body schema via the route's `openapi_extra`.** Keeps + runtime parsing untouched and overrides only the published requestBody schema + with one derived from the SDK write models. Lower runtime risk than (1), but it + introduces a second source of truth for the contract that must be kept in sync + with the write models, and it still rewrites a broadly-consumed published + artifact. Needs its own tests asserting the published `kind` enum and the + absence of non-settable fields. + +### Recommended approach + +Prefer (1) if the downstream conversion can be introduced cleanly: declare the +`/api/schema/load` request as the generated write models and convert to the +internal `SchemaRoot` models in one place at the top of the handler. This removes +the dual-model drift risk and makes the runtime gate and the published contract +share one source of truth. Fall back to (2) (`openapi_extra`) if the conversion +proves too invasive. + +### Acceptance check + +After the fix, regenerating with `uv run invoke schema.generate-jsonschema` must +show, for the load request body: + +- `kind` on the attribute/relationship schema published with its `enum` of allowed + values, and +- no non-settable fields (`id`, `state`, `inherited`, and other read-level/internal + fields) in the request body, + +with all existing backend and SDK tests still green and `uv run invoke docs.validate` +clean. diff --git a/dev/specs/002-user-facing-schema/opsmill-implement-report.md b/dev/specs/002-user-facing-schema/opsmill-implement-report.md new file mode 100644 index 00000000000..9daf1d0c488 --- /dev/null +++ b/dev/specs/002-user-facing-schema/opsmill-implement-report.md @@ -0,0 +1,82 @@ +# Implementation Report: User-Facing Schema Separation (INFP-234) — INCOMPLETE + +**Feature**: User-Facing Schema Separation +**Spec dir**: `specs/002-user-facing-schema` (tracked at `dev/specs/002-user-facing-schema`) +**Base commit**: `3525b42ec` +**Head commit**: `f39a8446c` (parent) · SDK submodule `ce6e067` +**Branch**: `dga/user-schema-infp-234-gqj6d` (+ SDK submodule own history) +**Status**: **INCOMPLETE** — 30/31 tasks done; T020 intentionally partial, and one HIGH review finding (REST OpenAPI contract) is deferred as a documented follow-up. All local-pass evidence is present (no MISSING rows). + +## 1. Chunk-by-chunk ledger + +| # | Chunk (phase) | Tasks | ✅ | ⚠️ | ❌ | Commit(s) | +|---|---------------|-------|----|----|----|-----------| +| 1 | Setup | T001–T003 | 3 | 0 | 0 | `1a6d1e648` | +| 2 | Foundational | T004–T009 | 6 | 0 | 0 | parent `581f8772e` / SDK `1aa2046` | +| 3 | US1 (P1 MVP) | T010–T016 | 7 | 0 | 0 | `4a5ccd27f` | +| 4 | US2 | T017–T021 | 4 | 1 | 0 | parent `6ca3ba3ee` / SDK `8d8da84` | +| 5 | US3 | T022–T026 | 5 | 0 | 0 | `e390fbd0b` | +| 6 | Polish | T027–T031 | 5 | 0 | 0 | parent `97c1e64c7` / SDK `5f6ac8a` | +| R | Review fixes | (FIX 1–5) | 4 | 1 | 0 | parent `f39a8446c` / SDK `ce6e067` | + +**Flagged upward during chunks:** +- **Ch2**: `tasks/backend.py` passed a *shell-escaped* repo path to Jinja's `FileSystemLoader`/`Path` — broke generation in worktree paths containing regex/shell metacharacters (this worktree). Switched Python-side FS/template access to the real `REPO_BASE` (no-op in clean CI paths). Used a dedicated `generate_schema_sdk.j2` so the internal variant stays byte-identical. Enums rendered as `Literal[...]` for self-contained SDK models. +- **Ch2/Ch6**: `backend.generate` regenerates `python_sdk/infrahub_sdk/protocols.py`, which showed pre-existing drift unrelated to this feature (pinned submodule predated current backend schema); committed in the SDK so CI's generated-file check passes. +- **Ch3**: T013 wired as a `model_validator(mode="before")` boundary gate (not a full model swap) — a full swap would 422 on the unknown field before naming `inherited`. +- **Ch4**: T020 left **partial** deliberately (see §3). Backend load boundary now calls the SDK's `validate_schema()` directly (single implementation). +- **Ch5**: backend internal Pydantic models already have the read model's field set; the "internal back-reference" is a meta-schema relationship, never serialized — GET was already read-model-consistent, so T025 enforced visibility via test rather than a risky rebase. + +## 2. Tasks not completed + +- **T020 (US2) — ⚠️ partial.** DONE: the generated write models are the single source for the load boundary (SDK `validate_schema` + backend; backend's duplicated validator removed). NOT DONE: the SDK's hand-written `main.py` models (`SchemaRoot`/`NodeSchema`/`*API` read models with ~15 behavior methods, enums, `BranchSchema`) and the ~22 in-SDK consumers are not repointed. Reason: the generated read models lack `hash` and differ in field set/typing (`Literal` vs enum classes; extra fields; `min_count`/`max_count` required int) from the hand-written read models; a naive rebase changes serialization shape and breaks export/protocols-generator/node consumers and the SDK suite. Deferred to keep the SDK suite green. Consequence: **FR-009** ("no second parallel definition") is not fully met — a behavior-subclass-over-generated-data-model refactor is needed in its own chunk. + +## 3. Local-pass evidence + +All rows observed passing locally (Neo4j/testcontainers Docker stack available for functional tests). No MISSING rows. + +| Test id | Type | Run command | Passed at (UTC) | Env | Verbatim pass line | +|---------|------|-------------|-----------------|-----|--------------------| +| `backend/tests/unit/core/schema/test_generated_visibility.py` (13 cases: SC-001 no-leak + SC-002 enum-published per family + positive settable-field guard) | unit | `uv run pytest backend/tests/unit/core/schema/test_generated_visibility.py -q` | 2026-07-03T15:28:39Z | backend unit, no DB | `13 passed` | +| `test_load_schema.py::…rejects_non_write_and_unknown_fields` | functional | `uv run pytest …::test_schema_load_rejects_non_write_and_unknown_fields -q` | 2026-07-03T14:18:51Z | Neo4j testcontainers | `2 passed … in 55.30s` (with out-of-enum kind) | +| `test_load_schema.py::…rejects_out_of_enum_attribute_kind` | functional | (run with above) | 2026-07-03T14:18:51Z | Neo4j testcontainers | `2 passed … in 55.30s` | +| `test_load_schema.py::…rejects_non_write_fields_in_extensions` (FIX 1) | functional | `uv run pytest …::test_schema_load_rejects_non_write_fields_in_extensions …::test_schema_load_rejects_out_of_enum_relationship_cardinality -v` | 2026-07-03T15:29:17Z | Neo4j testcontainers | `2 passed … in 50.08s` | +| `test_load_schema.py::…rejects_out_of_enum_relationship_cardinality` (FIX 2) | functional | (run with above) | 2026-07-03T15:29:17Z | Neo4j testcontainers | `2 passed … in 50.08s` | +| `test_load_schema.py::…stored_schema_with_read_level_field_reads_back` | functional | `uv run pytest …::test_stored_schema_with_read_level_field_reads_back …::test_schema_load_id_cannot_bypass_authorization -q` | 2026-07-03T~14:20Z | Neo4j testcontainers | `2 passed … in 60.33s` | +| `test_load_schema.py::…id_cannot_bypass_authorization` (R1) | functional | (run with above) | 2026-07-03T~14:20Z | Neo4j testcontainers | `2 passed … in 60.33s` | +| `test_load_schema.py` (full file, regression incl. parity test) | functional | `uv run pytest backend/tests/functional/api/test_load_schema.py` | 2026-07-03T15:32:00Z | Neo4j testcontainers | `17 passed … in 134.21s` | +| `test_40_schema.py::test_schema_read_endpoint_visibility` | component | `uv run pytest …::test_schema_read_endpoint_visibility -q` | 2026-07-03T~14:40Z | Neo4j testcontainers | `1 passed … in 23.59s` | +| `test_40_schema.py` (full file, regression) | component | `uv run pytest backend/tests/component/api/test_40_schema.py` | 2026-07-03T~14:45Z | Neo4j testcontainers | `27 passed … in 85.65s` | +| `python_sdk/tests/unit/test_schema_offline_validation.py` (14 cases incl. extensions + relationship + read-level breadth) | unit (SDK) | `uv run pytest tests/unit/test_schema_offline_validation.py -q` | 2026-07-03T15:36:35Z | SDK venv, pydantic only (no server) | `14 passed` | +| `python_sdk/tests/unit/test_schema_generated_models.py` (drift/presence/invariants) | unit (SDK) | `uv run pytest tests/unit/test_schema_generated_models.py -q` | 2026-07-03T~15:36Z | SDK venv | passed (with offline suite) | +| SDK suite regression (T020 gate) | unit (SDK) | `uv run pytest tests/unit -o addopts=""` | 2026-07-03T~13:00Z | SDK venv | `1432 passed` (10 pre-existing/environmental failures, unchanged from baseline; +23 new passing) | + +## 4. Review findings + +| Severity | File | Summary | Disposition | +|----------|------|---------|-------------| +| HIGH | `schema/openapi.json` / `backend/infrahub/api/schema.py` | REST OpenAPI load request still advertises bare-string `kind` (no enum) + non-settable fields; runtime rejects them via the before-validator (invisible to FastAPI schema-gen). Doc-vs-behavior gap; FR-004/SC-002 only met at SDK-model level. | **Deferred** — documented in `opsmill-implement-followups.md` with recommended approach. Not force-fixed (both safe paths risk the downstream parsing the impl deliberately avoided). | +| HIGH | `python_sdk/infrahub_sdk/schema/validate.py` | `extensions.nodes[*]` attributes/relationships bypassed the write-contract gate. | **Fixed inline** (FIX 1): `validate_schema` now gates extension attrs/rels against the generated write models; functional + SDK tests added. | +| MEDIUM | `backend/.../test_generated_visibility.py` | No positive guard — a future field defaulting to INTERNAL would silently drop from write+read and be rejected on load with no failing test. | **Fixed inline** (FIX 3): positive settable-field guard per family. | +| MEDIUM | tests | Relationship-level + non-`inherited` read-level rejection, and out-of-enum relationship fields, were untested. | **Fixed inline** (FIX 2). | +| MEDIUM | `test_load_schema.py` | Parity test is tautological (endpoint calls same validator); loose `in response.text` assertions. | New tests assert structured dotted-path errors; parity remains a wiring guard (noted). | +| LOW | `tasks/backend.py` | `ruff` invocations interpolated unescaped paths → break on shell-metacharacter checkout paths. | **Fixed inline** (FIX 4): paths quoted. | +| LOW | new test files | Spec/ticket IDs in test names/docstrings violate `.agents/rules`. | **Fixed inline** (FIX 4): removed. | + +## 5. Autonomous decisions (may warrant a look) + +1. **`before_specify` Jira hook run in `--dry-run`** (prep phase) to avoid creating/switching a second branch inside the existing worktree. +2. **`ESCAPED_REPO_PATH`→`REPO_BASE`** in `tasks/backend.py`: a real fix (escaped path broke Jinja/Path here), no-op on clean CI paths — but it is a change to shared generation tooling beyond the feature's strict scope. +3. **Unrelated `protocols.py` drift** committed in the SDK to keep the generated-file CI check green. +4. **T020 left partial** to keep the SDK test suite green rather than ship a broken library (see §2). +5. **T025 via visibility-enforcing test, not a model rebase** (backend internal models already match the read field set). +6. **FIX 5 (REST OpenAPI, HIGH) deferred** rather than force-fixed in a review pass — documented follow-up. +7. **SDK commits are on the submodule's detached HEAD** (consistent across chunks). To land them, a branch must be created in the SDK repo (`opsmill/infrahub-sdk-python`) at `ce6e067` and a PR opened there; the parent repo's submodule pointer references it. +8. Targeted `ruff`/`mypy` on changed files were used in the review-fix pass instead of repo-wide `invoke format`/`lint` (which Ch6 already ran green across the whole tree). + +## 6. Suggested next steps + +1. **Address the deferred HIGH finding** — publish the write contract in the REST OpenAPI (`opsmill-implement-followups.md`): declare the load endpoint's request model as the generated write models (converting to internal downstream) or inject a custom OpenAPI request-body schema, then regenerate `schema/openapi.json`. This is what makes FR-004/SC-002 true for an agent reading the REST contract (the P1 story's artifact). +2. **Finish T020** — consolidate the SDK's hand-written models onto the generated data models via a behavior-subclass/mixin strategy (reconciling `hash` and the `Literal`-vs-enum/field-set deltas), repoint the ~22 consumers, keep the SDK suite green → satisfies FR-009. +3. **Land the SDK changes** — create a branch in the SDK repo at `ce6e067`, open its PR, then bump the submodule pointer in the backend PR. +4. **Set the SC-004 benchmark target** with product (still open). +5. Open the backend PR from `dga/user-schema-infp-234-gqj6d` once 1–3 are resolved; run the full repo-wide `invoke format`/`lint`/`docs.validate` in CI. diff --git a/dev/specs/002-user-facing-schema/plan.md b/dev/specs/002-user-facing-schema/plan.md new file mode 100644 index 00000000000..07d1ad78834 --- /dev/null +++ b/dev/specs/002-user-facing-schema/plan.md @@ -0,0 +1,99 @@ +# Implementation Plan: User-Facing Schema Separation + +**Branch**: `user-facing-schema-infp-234` | **Date**: 2026-07-01 | **Spec**: [spec.md](./spec.md) + +**Input**: Feature specification from `specs/002-user-facing-schema/spec.md`; PRD and resolved field mapping at repo root (`PRD-user-facing-schema-separation.md`, `schema-field-classification.md`). + +## Summary + +Separate Infrahub's user-facing schema from its internal schema by generating, from the single source of truth (`backend/infrahub/core/schema/definitions/internal.py`), three model families instead of one: a **write** model (exactly what `/api/schema/load` accepts), a **read** model (what `/api/schema` returns — a superset that adds visible-but-not-settable fields), and the existing **internal** model (unchanged, backend-only). Each field definition gains a `visibility` classification (`write`/`read`/`internal`, default `internal`) carried in the existing `extra={}` channel. Fields with a known allowed-value set (currently dropped during generation) publish that set into the write/read models. The write and read models are generated into the `python_sdk` submodule so they can validate a schema offline; the backend's API layer consumes those same SDK models, guaranteeing server/client parity. Because `extra="forbid"` is already enforced, a write model that simply omits non-write fields yields the required field-level rejection with minimal new validation code. + +## Technical Context + +**Language/Version**: Python 3.14 (backend + SDK) + +**Primary Dependencies**: Pydantic 2.12 (models), Jinja2 (code generation), FastAPI 0.131 (REST), Invoke 2.2 (task runner). `infrahub-sdk` is a path/editable dependency of the backend (`pyproject.toml:82`, submodule `python_sdk/`). + +**Storage**: N/A — no stored-data model change or migration. This is an API-model + code-generation change only. + +**Testing**: pytest. Unit (`backend/tests/unit`), component (`backend/tests/component`), functional (`backend/tests/functional/api`), plus SDK-side tests in the submodule. + +**Target Platform**: Linux server (backend) + published Python SDK (client, offline-capable). + +**Project Type**: Web service backend + shipped client library (SDK). No frontend surface this cycle. + +**Performance Goals**: Not a hot path — schema load/read is infrequent and human/agent-driven. Generation is a dev-time step. No specific latency target. + +**Constraints**: Generated files must be byte-stable (idempotent regeneration, validated in CI). SDK write/read models must import with zero backend dependency **and be committed, shipped artifacts in the SDK package** (not build-time-only), so a consumer installing only the SDK obtains them. Backward compatibility: previously loadable write-shaped schemas must still load; stored schemas must still read back. Server and SDK ship one contract and must be released compatibly; the submission `version` field is the skew anchor (local validation advisory, server authoritative). + +**Scale/Scope**: Four schema families (node, generic, attribute, relationship) × ~20–30 fields each; one generator template; two API endpoints; one SDK schema module. + +## Constitution Check + +*GATE: evaluated pre-research and re-checked post-design.* + +- **I. Schema-Driven Integrity** — ✅ Advances it. Reduces invalid schemas entering the system; no bypass of the schema layer; generated files remain generated (never hand-edited). +- **II. Branch-Safe by Default** — ✅ N/A to data paths. No query or temporal behaviour changes; schema load already runs through branch-aware processing which is untouched. +- **III. Type Safety & Explicit Contracts** — ✅ Directly advances the principle ("REST contracts MUST be defined before implementation; generated types MUST be used by consumers"). The write/read models are the explicit contract, generated and shared by server and SDK. +- **IV. Test Discipline** — ✅ Unit (generator: idempotency, per-model membership, enum propagation), functional (API load rejection + read serialization), SDK offline validation, and server/SDK parity contract test. Frontend E2E is **N/A** (no user-facing UI this cycle) — justified below. +- **V. Query Performance & Efficiency** — ✅ N/A. No new queries. +- **VI. Security & Input Boundaries** — ✅ Advances it. Hard rejection at the API boundary; input validated by generated models; no injection surface introduced. +- **VII. Simplicity & Maintainability** — ✅ Single source of truth → three generated outputs; the SDK's hand-written schema models are *removed* (net reduction of parallel definitions), not multiplied. The added generation variants are justified below. + +**Gate result: PASS.** No unjustified violations. See Complexity Tracking for the two justified additions. + +### Frontend E2E justification (Principle IV) + +The constitution requires frontend E2E for user-facing features. This feature has **no frontend surface** this cycle (the `schema-visualizer` consumer is explicitly out of scope). The "users" are API/SDK clients and agents. Coverage is therefore backend functional + SDK offline + contract-parity tests. If/when the visualizer consumes the new models, E2E is added then. + +## Project Structure + +### Documentation (this feature) + +```text +specs/002-user-facing-schema/ +├── plan.md # This file +├── research.md # Phase 0 output +├── data-model.md # Phase 1 output +├── quickstart.md # Phase 1 output +├── contracts/ # Phase 1 output +│ ├── schema-models.md # write/read/internal model shapes + visibility rules +│ └── api-and-sdk.md # load rejection, read serialization, SDK offline validation +├── spec.md +└── tasks.md # Phase 2 output (/speckit-tasks — not created here) +``` + +### Source Code (repository root) + +```text +backend/ +├── infrahub/core/schema/ +│ ├── definitions/internal.py # SOURCE OF TRUTH — add `visibility` to ExtraField + set per field +│ ├── generated/ # internal models (unchanged location; regenerated) +│ └── __init__.py, *_schema.py # rich internal wrapper models (unchanged shape) +├── infrahub/api/schema.py # load (POST) + read (GET) — consume SDK write/read models +├── infrahub/types.py, core/constants # ATTRIBUTE_KIND_LABELS, RelationshipKind, etc. (enum sources) +├── templates/ +│ └── generate_schema.j2 (+ variant/param + enum rendering) # generator template(s) +└── tasks/backend.py :: _generate_schemas # emit write/read families into python_sdk + +python_sdk/ # git submodule (must be checked out for this work) +└── infrahub_sdk/schema/ # REPLACE hand-written schema models with generated write/read + # + offline validation entry point + +tests/ +├── backend/tests/unit/… # generator unit tests +├── backend/tests/functional/api/test_load_schema.py # load rejection (extend) +├── backend/tests/component/api/test_40_schema.py # read serialization (extend) +├── backend/tests/component/core/test_schema.py # model validation (extend) +└── python_sdk/… # SDK offline-validation tests +``` + +**Structure Decision**: Web-service backend + client library. The backend hosts the source-of-truth definitions and the generator; the SDK hosts the generated external (write/read) models; the internal models stay in the backend. This mirrors the existing `protocols.py` generation that already writes into `python_sdk/infrahub_sdk/protocols.py`. + +## Complexity Tracking + +| Addition | Why Needed | Simpler Alternative Rejected Because | +|----------|------------|-------------------------------------| +| Generating three model families instead of one | The explicit user-facing contract (Principle III) and rejection of non-settable fields require distinct write/read shapes | A single model with runtime filtering cannot express "field absent from the write contract" in a JSON-schema an agent reads, and cannot leverage the existing `extra="forbid"` rejection | +| Backend imports SDK-generated write/read models (inverts today's direction) | Guarantees server/client parity with one implementation; enables offline SDK validation (FR-006/7/8) | Two independently-generated copies (one per side) would be identical by construction but reintroduce a drift-review burden and a second contract to keep honest; the explicit decision (PRD) is single shared models | diff --git a/dev/specs/002-user-facing-schema/quickstart.md b/dev/specs/002-user-facing-schema/quickstart.md new file mode 100644 index 00000000000..6172422cdb7 --- /dev/null +++ b/dev/specs/002-user-facing-schema/quickstart.md @@ -0,0 +1,73 @@ +# Quickstart / Validation Guide: User-Facing Schema Separation + +Runnable scenarios that prove the feature works end-to-end. See `contracts/` and +`data-model.md` for the shapes referenced here. + +## Prerequisites + +- `uv sync --all-groups` (backend deps). +- `git submodule update --init python_sdk` — the SDK submodule must be checked out + (it is empty in a fresh worktree); the write/read models are generated into it. + +## 1. Regenerate the models (idempotency) + +```bash +uv run invoke backend.generate +uv run invoke schema.generate-jsonschema # refresh schema/openapi.json +git status --short # expect: only intended generated diffs +uv run invoke backend.generate # run again +git diff --exit-code # expect: no diff (idempotent) +``` + +Expected: three model families are generated; the SDK now contains generated +write/read schema models; a second run produces no diff. + +## 2. Enum propagation (FR-004 / SC-002) + +```bash +# Inspect the generated write JSON-schema for attribute `kind` +uv run invoke schema.generate-jsonschema +python -c "import json,sys; d=json.load(open('schema/openapi.json')); print('kind enum present')" +``` + +Expected: attribute `kind` and relationship `kind`/`cardinality` carry their full +allowed-value lists in the generated write model / JSON-schema; no constrained field +is a bare `str` where an enum is defined internally. + +## 3. Reject non-settable fields on load (FR-003 / SC-003) + +Run the functional load tests (extended for this feature): + +```bash +uv run invoke backend.test-unit # generator unit tests +uv run pytest backend/tests/functional/api/test_load_schema.py -q +``` + +Expected: a payload containing `inherited` (read-level) plus an unknown field is +rejected with a field-level error naming each; a valid write-shaped schema loads. + +## 4. Read-back includes read-only fields (FR-005/FR-006/FR-010) + +```bash +uv run pytest backend/tests/component/api/test_40_schema.py -q +``` + +Expected: `GET /api/schema` returns `inherited`/`used_by`; never returns internal +fields; a pre-existing stored schema reads back without error. + +## 5. SDK offline validation + parity (FR-006/FR-007/FR-008 / SC-005) + +```bash +# In an environment with ONLY the SDK installed (no server): +cd python_sdk && uv run pytest tests -k "schema and validate" -q +``` + +Expected: a good schema validates locally; a bad one fails naming the field; the +local verdict matches the server's for the same payloads. + +## Done-when + +- All five scenarios pass. +- `uv run invoke backend.generate && uv run invoke schema.generate-jsonschema` leaves + no uncommitted drift. +- SDK's former hand-written schema models are gone; callers use the generated ones. diff --git a/dev/specs/002-user-facing-schema/research.md b/dev/specs/002-user-facing-schema/research.md new file mode 100644 index 00000000000..33e990fdae1 --- /dev/null +++ b/dev/specs/002-user-facing-schema/research.md @@ -0,0 +1,127 @@ +# Research: User-Facing Schema Separation + +Phase 0 decisions. All resolved; no open NEEDS CLARIFICATION blocking planning (the one remaining spec marker — SC-004 benchmark target — is a product metric, not a technical unknown). + +## D1 — How to produce three model families from one source + +**Decision**: Parameterize generation by `visibility` and emit write/read families in addition to the existing internal family, from the same `internal.py` definitions, via the existing Jinja generator. + +**Rationale**: The generator (`tasks/backend.py::_generate_schemas`) already renders all model files from a single template (`backend/templates/generate_schema.j2`) parameterized by `schema`/`node`/`parent`. Adding a `visibility` filter over `node.attributes` (and the structural relationships) plus a variant name is a localized extension. The `without_duplicates()` base/derived split already in place is reused per variant. + +**Alternatives considered**: +- *Runtime filtering of one model* — rejected: cannot produce a JSON-schema that omits non-write fields (agents read the omitted-field contract), and cannot reuse `extra="forbid"` for rejection. +- *Hand-written second model set* — rejected: violates single-source (Principle VII) and reintroduces drift. + +## D2 — Where the classification lives + +**Decision**: Extend `ExtraField` (TypedDict) in `internal.py` with a `visibility` key (an ordinal enum `internal < read < write`), set it per `SchemaAttribute`/`SchemaRelationship`, default `internal` when unset. Orthogonal to the existing `update:` axis. + +**Rationale**: `extra={}` already flows into generation end-to-end (proven by `update:` → `json_schema_extra`). Reusing it keeps the change in one place. The resolved per-field mapping is in `schema-field-classification.md`. + +**Alternatives considered**: a separate parallel table keyed by field name — rejected: drifts from the definitions it describes. + +## D3 — How rejection of non-write fields works + +**Decision**: Rely on the existing `model_config = ConfigDict(extra="forbid")` on `SchemaRoot` and node models. The write model simply omits `read`/`internal` fields; submitting them then triggers Pydantic's extra-field rejection, which names the field. Add a targeted check only where the message needs to be clearer (e.g. distinguishing "read-only field" from "unknown field"). + +**Rationale**: `extra="forbid"` is already enforced (`core/schema/__init__.py:64`). This means most of FR-003 is achieved structurally by generating the right write shape — minimal new validation code. Confirmed: unknown fields are already rejected today; the bug is only that `inherited`/etc. are *known* fields on the current model. + +**Alternatives considered**: a bespoke `model_validator(mode="before")` enumerating disallowed fields — kept as a thin enhancement only for message quality, not as the primary mechanism. + +## D4 — How to propagate enums / allowed-value sets + +**Decision**: Render the allowed-value set into the write/read field so it appears in the emitted JSON-schema. For fields whose `internal_kind` is an Enum class (e.g. `BranchSupportType`, `RelationshipKind`, `RelationshipCardinality`, `RelationshipDirection`, `RelationshipDeleteBehavior`, `AllowOverrideType`, `SchemaAttributeDisplay`, `HashableModelState`), type the field as that enum. For list-backed sets (`ATTRIBUTE_KIND_LABELS`), render a `Literal[...]` (or attach `json_schema_extra={"enum": [...]}`) so the JSON-schema carries the values. + +**Rationale**: Today the template drops `enum=` entirely and relies on a runtime `field_validator` (`attribute_schema.py:104`) that only fires internally. Putting the constraint in the write/read model means the JSON-schema an agent reads is complete (FR-004/SC-002) and validation is automatic on both server and SDK. + +**Alternatives considered**: keep bare `str` + document allowed values elsewhere — rejected: defeats the feature's core purpose (machine-readable completeness). + +## D5 — Backend consumes SDK-hosted write/read models + +**Decision**: Generate write/read models into `python_sdk/infrahub_sdk/schema/`, replacing the SDK's current hand-written schema models. The backend's `api/schema.py` imports these SDK models for `/api/schema/load` validation and `/api/schema` serialization, replacing the current `SchemaLoadAPI(SchemaRoot)` / `APINodeSchema(NodeSchema)` derivations. The rich **internal** models in `backend/infrahub/core/schema/` stay as-is. + +**Rationale**: This is the explicit PRD decision (single implementation → parity). It inverts today's direction (backend currently owns models; SDK mirrors them) but is safe because generation is a dev-time step and the `protocols.py`-into-SDK precedent already exists. The backend keeps depending on the SDK at runtime (already the case via the editable path dependency). + +**Risk / dependency**: the `python_sdk` submodule is **not checked out in this worktree**. An audit-and-replace of the SDK's existing `NodeSchemaAPI`/`SchemaRootAPI`/`GenericSchemaAPI` models must happen against the checked-out submodule (first implementation task). Build ordering: `backend.generate` reads backend `internal.py` → writes SDK schema files → backend imports them. `APISchemaMixin.set_kind` (injects `kind` from `namespace`+`name`, `mode="before"`) stays compatible and moves to sit with the read model. + +**Alternatives considered**: two generated copies (backend + SDK) — rejected per Complexity Tracking (drift-review burden). + +## D6 — Backward compatibility & round-trip + +**Decision**: Accept that read-modify-write round-trips via the API break this cycle (read-only fields now rejected on load); document the mitigation (clients strip against the published write schema); defer the write-shaped export to a later cycle. + +**Rationale**: Matches the spec's edge-case analysis; the dominant authoring path is write-shaped YAML. Stored-schema read-back stays safe because read ⊇ write. + +## Prior art (tests to extend) + +- `backend/tests/functional/api/test_load_schema.py` — load endpoint (idempotency, absent/delete, extensions). +- `backend/tests/component/api/test_40_schema.py` — GET /api/schema read. +- `backend/tests/component/core/test_schema.py` — model validation + deprecation warnings. +- SDK submodule schema tests — for offline validation. + +## SDK audit (T002) + +Audited `python_sdk/infrahub_sdk/schema/` at submodule commit `38216f3` (`v1.13.2-939-g38216f3`). The data models live in `main.py`; `__init__.py` re-exports them and adds the `InfrahubSchema[Sync]` client and type aliases; `export.py`/`repository.py` are adjacent (repository config is out of scope for this feature). + +### The SDK already ships two parallel model families + +This is the central finding: the SDK **already** splits write vs read by hand, so the planned generated write/read split maps onto existing structure rather than inventing it. + +| Concern | Write / input (no suffix) | Read / API (`API` suffix) | +|---|---|---| +| Root | `SchemaRoot` (`version`, `generics`, `nodes`, `node_extensions`) | `SchemaRootAPI` (`main` hash, `generics`, `nodes`, `profiles`, `templates`) | +| Node | `NodeSchema` | `NodeSchemaAPI` (+ `hash`, `hierarchy`) | +| Generic | `GenericSchema` | `GenericSchemaAPI` (+ `hash`, `hierarchical`, `used_by`, `restricted_namespaces`) | +| Attribute | `AttributeSchema` | `AttributeSchemaAPI` (+ `inherited`, `read_only`, `allow_override`) | +| Relationship | `RelationshipSchema` | `RelationshipSchemaAPI` (+ `inherited`, `read_only`, `hierarchical`, `allow_override`) | +| Attr/rel container | `BaseSchemaAttrRel` | `BaseSchemaAttrRelAPI` | +| Profile / Template | — (read-only concepts) | `ProfileSchemaAPI`, `TemplateSchemaAPI` | +| Shared base | `BaseSchema`, `BaseNodeSchema` (shared by both) | same | + +Mapping to the planned generated models: **write model ⇐ the no-suffix family**, **read model ⇐ the `API` family**. The read family is a strict superset of write (read ⊇ write), matching D6. `NodeSchema.convert_api()` / `GenericSchema.convert_api()` already do write→read upcasting. + +### Fields (write vs the read-only additions) + +- `AttributeSchema` (write): `id, state, name, kind, label, description, default_value, unique, branch, optional, choices, enum, max_length, min_length, regex, order_weight`. Read adds `inherited, read_only, allow_override`. +- `RelationshipSchema` (write): `id, state, name, peer, kind, label, description, identifier, min_count, max_count, direction, on_delete, cardinality, branch, optional, order_weight`. Read adds `inherited, read_only, hierarchical, allow_override`. +- `BaseSchema` (shared): `id, state, name, label, namespace, description, include_in_menu, menu_placement, display_label, display_labels, human_friendly_id, icon, uniqueness_constraints, documentation, order_by`. +- `BaseNodeSchema` (shared): + `inherit_from, branch, default_filter, generate_profile, generate_template, parent, children`. `NodeSchemaAPI` read-adds `hash, hierarchy`. +- `GenericSchemaAPI` read-adds `hash, hierarchical, used_by, restricted_namespaces`. + +### `model_config` + +Every model uses `model_config = ConfigDict(use_enum_values=True)` — and nothing else. Notably **none of these models set `extra="forbid"`** (only the unrelated `repository.py` config models do). So the SDK today does **not** reject unknown fields — that strictness is new work (T013/T019). The generated write/read models must preserve `use_enum_values=True` (or equivalently serialize enums to values). + +### Enums (defined in `main.py`, referenced by fields) + +`RelationshipCardinality`, `BranchSupportType`, `RelationshipKind`, `RelationshipDirection`, `AttributeKind`, `SchemaState`, `AllowOverrideType`, `RelationshipDeleteBehavior`. Caveat: `AttributeKind` has a custom `__getattr__` emitting a `DeprecationWarning` for `STRING`; generation must not clobber that behavior. This is the enum inventory T007 must propagate into the generated JSON-schema. + +### Behavior attached to the models (the main T020 risk) + +The `API` models are **not** plain data — they carry substantial behavior that generated Pydantic models will not have: + +- `BaseSchemaAttrRelAPI`: ~15 helpers — `get_field`, `get_attribute[_or_none]`, `get_relationship[_or_none]`, `get_relationship_by_identifier`, `get_matching_relationship`, and properties `attribute_names`, `relationship_names`, `mandatory_input_names`, `mandatory_attribute_names`, `mandatory_relationship_names`, `local_attributes`, `local_relationships`, `unique_attributes`. +- `BaseSchema`: properties `kind`, `supports_artifact_definition`, `supports_artifacts`, `supports_file_object`, `supports_hierarchy`, `hierarchical_relationship_schemas` (default/overridable). +- `NodeSchemaAPI`: overrides `supports_artifacts/file_object/hierarchy` and `hierarchical_relationship_schemas` (synthesizes parent/children/ancestors/descendants pseudo-relationships). +- `RelationshipSchemaAPI`: `cardinality_is_one` / `cardinality_is_many`. +- `SchemaRoot.to_schema_dict()`, `BranchSchema.from_api_response` / `from_schema_root_api`. + +Implication for T020: a pure code-gen swap will drop these methods. The removal must either (a) generate plain data base models and keep hand-written behavior subclasses/mixins that inherit them, or (b) move the behavior onto wrappers. This is the biggest design decision deferred to T020 and should be settled before that chunk. + +### In-SDK consumers (22 files) + +Public surface is `infrahub_sdk.schema.__all__` (17 names) plus deep imports from `infrahub_sdk.schema.main`. Type aliases in `__init__.py`: `MainSchemaTypes = NodeSchema | GenericSchema` (write), `MainSchemaTypesAPI = NodeSchemaAPI | GenericSchemaAPI | ProfileSchemaAPI | TemplateSchemaAPI` (read), `MainSchemaTypesAll`. Consumers include `client.py`, `node/*` (`node.py`, `relationship.py`, `attribute.py`, `related_node.py`), `ctl/*` (schema, render, transform, repository, generator, check, utils, object/*, cli_commands), `spec/object.py`, `transfer/*`, `graphql/query_renderer.py`, `protocols_generator/generator.py`, `protocols_base.py`, `query_groups.py`, `pytest_plugin/utils.py`, `testing/schemas/*`. T020 must keep these import paths stable "where feasible" — favour re-exporting generated models under the same names in `schema/main.py` / `schema/__init__.py`. + +### Discrepancies to fix during generation (T006/T007) + +- `RelationshipSchema.cardinality: str = "many"` is a bare `str`, not the `RelationshipCardinality` enum — generation (T007) should emit the enum/`Literal`. Existing gap. +- Write `SchemaRoot` and read `SchemaRootAPI` are **not symmetric** (`version`/`node_extensions` vs `main`/`profiles`/`templates`). The generated write root should reflect the load contract (`version` + `nodes`/`generics`), not mirror the read root. +- `NodeExtensionSchema` exists on the write side (`SchemaRoot.node_extensions`) with no read equivalent; decide whether the generated write model retains node-extensions or the load path keeps a hand-written extension model. + +## Towncrier setup (T003) + +`[tool.towncrier]` in `pyproject.toml`: fragments live in `changelog/` with orphan prefix `+`, template `changelog/towncrier.md.template`, filename `CHANGELOG.md`. Fragment naming is `+..md`. + +Valid fragment **types**: `security`, `removed`, `deprecated`, `added`, `changed`, `fixed`, `housekeeping`. + +There is **no dedicated "breaking" type**. The T027 breaking-change fragment for the stricter `POST /api/schema/load` behaviour should therefore use type **`changed`** (`changelog/+.changed.md`), consistent with existing fragments (e.g. `+graphql-error-catalogue.changed.md`). diff --git a/dev/specs/002-user-facing-schema/schema-field-classification.md b/dev/specs/002-user-facing-schema/schema-field-classification.md new file mode 100644 index 00000000000..02d19781f3d --- /dev/null +++ b/dev/specs/002-user-facing-schema/schema-field-classification.md @@ -0,0 +1,193 @@ +# Schema Field Classification (INFP-234) — First Proposal + +Companion to `PRD-user-facing-schema-separation.md`. Every field in the schema +definitions (`backend/infrahub/core/schema/definitions/internal.py`) is listed +with a **proposed** `visibility` classification. This is my first pass — review +and correct the calls, especially the ⚠ rows. + +## Legend + +| Visibility | Meaning | Appears in | +| --- | --- | --- | +| **write** | User authors it. Accepted on `/api/schema/load`. | write + read + internal | +| **read** | User may see it but not set it (system-derived). Rejected on write. | read + internal | +| **internal** | Never exposed to users at all. | internal only | + +Nesting holds: `write ⊆ read ⊆ internal`. Default for any unclassified field = `internal`. + +## Method & one important caveat + +I used the existing `update:` hint as a *signal*, but it is **not** the same axis +and must not be used as authority. `update:` = "can this change after creation?". +`visibility` = "who may set it in the first place?". Proof they're independent: +`state`, `attributes`, and `relationships` are all `update: NOT_APPLICABLE` yet are +squarely **write** (they're the core of what a user submits). So do not assume +`NOT_APPLICABLE` ⇒ hidden. + +Notable consequence of this pass: **almost nothing is truly `internal`.** The +system-derived fields that remain (`inherited`, `used_by`) are still useful to +*see* on read, so they land in `read`. The only pure-`internal` item found is the +`node` back-reference relationship. The big open question for you is whether the +few `read` rows below should actually be fully hidden (`internal`). + +--- + +## 1. Base node fields (shared by **Node** and **Generic**) + +| Field | Kind | `update:` | Proposed | Notes | +| --- | --- | --- | --- | --- | +| `id` | Text | not_applicable | **write** | System-assigned on create, but a user supplies it on an existing node to drive a rename or delete. | +| `name` | Text | migration_required | **write** | Core user field. | +| `namespace` | Text | migration_required | **write** | Core user field. | +| `description` | Text | allowed | **write** | | +| `label` | Text | allowed | **write** | Autogenerated if omitted, but user-settable. | +| `branch` | Text (enum BranchSupportType) | not_supported | **write** | Set at create; enum must propagate. `not_supported` = can't change later, still user-set. | +| `default_filter` | Text | allowed | **write** | Deprecated (use `human_friendly_id`). | +| `human_friendly_id` | List[str] | allowed | **write** | | +| `display_label` | Text | allowed | **write** | | +| `display_labels` | List[str] | allowed | **write** | Deprecated. | +| `include_in_menu` | Boolean | allowed | **write** | | +| `menu_placement` | Text | allowed | **write** | | +| `icon` | Text | allowed | **write** | | +| `order_by` | List[str] | allowed | **write** | | +| `uniqueness_constraints` | List[list[str]] | migration_required | **write** | | +| `documentation` | URL | allowed | **write** | | +| `state` | Text (enum HashableModelState) | not_applicable | **write** ⚠ | Load directive (present/absent, e.g. to delete). Confirm we want users setting this in the write model. | +| `attributes` | List[AttributeSchema] | not_applicable | **write** | Structural — the node's attributes (see §4). | +| `relationships` | List[RelationshipSchema] | not_applicable | **write** | Structural — the node's relationships (see §5). | + +--- + +## 2. Node-only fields (on top of §1) + +| Field | Kind | `update:` | Proposed | Notes | +| --- | --- | --- | --- | --- | +| `inherit_from` | List[str] | validate_constraint | **write** | User lists generics to inherit. | +| `generate_profile` | Boolean | validate_constraint | **write** | | +| `generate_template` | Boolean | allowed | **write** | | +| `hierarchy` | Text | validate_constraint | **write** ⚠ | Description says *"Internal value to track the name of the Hierarchy"* — is this user-set for hierarchical nodes, or derived from `inherit_from`? If derived → `read`. | +| `parent` | Text | validate_constraint | **write** | Expected parent kind in a hierarchy. | +| `children` | Text | validate_constraint | **write** | Expected children kind in a hierarchy. | + +--- + +## 3. Generic-only fields (on top of §1) + +| Field | Kind | `update:` | Proposed | Notes | +| --- | --- | --- | --- | --- | +| `hierarchical` | Boolean | validate_constraint | **write** | Opt into hierarchical mode. | +| `generate_profile` | Boolean | validate_constraint | **write** | | +| `used_by` | List[str] | not_applicable | **read** | Computed: nodes referencing this generic. Visible, not settable. | +| `restricted_namespaces` | List[str] | allowed | **write** | | + +--- + +## 4. Attribute fields (`Schema/Attribute`) + +| Field | Kind | `update:` | Proposed | Notes | +| --- | --- | --- | --- | --- | +| `id` | Text | not_applicable | **write** | User supplies it on an existing attribute to drive a rename or delete. | +| `name` | Text | migration_required | **write** | | +| `kind` | Text (enum ATTRIBUTE_KIND_LABELS) | migration_required | **write** | **The headline enum** — must propagate the full kind list. | +| `enum` | List | validate_constraint | **write** | User-defined valid values. | +| `computed_attribute` | JSON (ComputedAttribute) | allowed | **write** | | +| `choices` | List[DropdownChoice] | validate_constraint | **write** | | +| `regex` | Text | validate_constraint | **write** | Deprecated (use `parameters.regex`). | +| `max_length` | Number | validate_constraint | **write** | Deprecated (use `parameters.max_length`). | +| `min_length` | Number | validate_constraint | **write** | Deprecated (use `parameters.min_length`). | +| `label` | Text | allowed | **write** | | +| `description` | Text | allowed | **write** | | +| `read_only` | Boolean | migration_required | **write** | Per our decision, stays user-settable. | +| `unique` | Boolean | migration_required | **write** | | +| `optional` | Boolean | migration_required | **write** | | +| `branch` | Text (enum BranchSupportType) | not_supported | **write** | | +| `order_weight` | Number | allowed | **write** | | +| `default_value` | Any | allowed | **write** | | +| `inherited` | Boolean | not_applicable | **read** | **Canonical read example** — set by the system on inheritance. | +| `state` | Text (enum HashableModelState) | not_applicable | **write** ⚠ | Load directive (see §1 `state`). | +| `allow_override` | Text (enum AllowOverrideType) | allowed | **write** | | +| `parameters` | JSON (AttributeParameters family) | validate_constraint | **write** | | +| `deprecation` | Text | allowed | **write** | | +| `display` | Text (enum SchemaAttributeDisplay) | allowed | **write** | | + +--- + +## 5. Relationship fields (`Schema/Relationship`) + +| Field | Kind | `update:` | Proposed | Notes | +| --- | --- | --- | --- | --- | +| `id` | Text | not_applicable | **write** | User supplies it on an existing relationship to drive a rename or delete. | +| `name` | Text | allowed | **write** | | +| `peer` | Text | validate_constraint | **write** | | +| `kind` | Text (enum RelationshipKind) | allowed | **write** | Enum must propagate. | +| `label` | Text | allowed | **write** | | +| `description` | Text | allowed | **write** | | +| `identifier` | Text | allowed | **write** ⚠ | Often auto-generated; user may set it to match both directions. Keep `write` unless we'd rather users never set it. | +| `cardinality` | Text (enum RelationshipCardinality) | validate_constraint | **write** | Enum must propagate. | +| `min_count` | Number | validate_constraint | **write** | | +| `max_count` | Number | validate_constraint | **write** | | +| `common_parent` | Text | validate_constraint | **write** | | +| `common_relatives` | List[str] | allowed | **write** | | +| `order_weight` | Number | allowed | **write** | | +| `optional` | Boolean | validate_constraint | **write** | | +| `branch` | Text (enum BranchSupportType) | not_supported | **write** | | +| `inherited` | Boolean | not_applicable | **read** | Set by the system on inheritance. | +| `direction` | Text (enum RelationshipDirection) | not_supported | **write** | | +| `hierarchical` | Text | not_supported | **read** ⚠ | Description says *"Internal attribute to track the type of hierarchy"* — likely derived from inheriting a hierarchical generic. Confirm read vs internal. | +| `state` | Text (enum HashableModelState) | not_applicable | **write** ⚠ | Load directive. | +| `on_delete` | Text (enum RelationshipDeleteBehavior) | allowed | **write** | | +| `allow_override` | Text (enum AllowOverrideType) | allowed | **write** | | +| `read_only` | Boolean | allowed | **write** | | +| `deprecation` | Text | allowed | **write** | | +| `display` | Text (enum SchemaAttributeDisplay) | allowed | **write** | | + +--- + +## 6. Structural relationships (schema-node graph edges) + +These are the `relationships=[...]` on each definition (distinct from the fields above). + +| On | Edge | Peer | Proposed | Notes | +| --- | --- | --- | --- | --- | +| Node / Generic | `attributes` | SchemaAttribute | **write** | The user's attribute list (same concept as §1 `attributes`). | +| Node / Generic | `relationships` | SchemaRelationship | **write** | The user's relationship list. | +| Attribute | `node` | SchemaNode (Parent) | **internal** | Back-reference to the owning node; implied by nesting, never in the user payload. **Only pure-internal item found.** | +| Relationship | `node` | SchemaNode (Parent) | **internal** | Same — parent back-reference. | + +> Note: `attributes`/`relationships` are defined *twice* in the source — once as +> `SchemaAttribute` entries in `base_node_schema` (excluded by `to_dict`) and once +> as structural relationships here. For the user-facing model they're one concept: +> the nested content. Flagging so the generator doesn't emit them twice. + +--- + +## Enums to propagate (FR-004 targets) + +Every field whose definition already carries `enum=` — these must surface their +allowed values in the write/read models instead of a bare type: + +- `kind` (attribute) → `ATTRIBUTE_KIND_LABELS` +- `kind` (relationship) → `RelationshipKind` +- `cardinality` → `RelationshipCardinality` +- `direction` → `RelationshipDirection` +- `on_delete` → `RelationshipDeleteBehavior` +- `branch` (base/attribute/relationship) → `BranchSupportType` +- `allow_override` (attribute/relationship) → `AllowOverrideType` +- `display` (attribute/relationship) → `SchemaAttributeDisplay` +- `state` (everywhere) → `HashableModelState` + +--- + +## Rows I most want you to review + +1. **`state`** (all three) — write vs. something more guarded. It's a load + directive, not really schema data. Keep in write model? +2. **`node.hierarchy`** and **`relationship.hierarchical`** — both self-describe as + "internal" but sit among user config. Derived → `read`, or user-set → `write`? +3. **`identifier`** (relationship) — auto-generated but overridable. Leave `write`? +4. **The `read` tier as a whole** (`inherited`, `used_by`) — do you want these + *visible-but-locked* (`read`), or fully hidden (`internal`)? This decides whether + the `read` model is meaningfully different from `internal`. +5. **Deprecated fields** (`default_filter`, `display_labels`, attribute + `regex`/`min_length`/`max_length`) — keep as `write`, or drop from the write + model to steer users toward the replacements? diff --git a/dev/specs/002-user-facing-schema/spec.md b/dev/specs/002-user-facing-schema/spec.md new file mode 100644 index 00000000000..cbf4d5bf46b --- /dev/null +++ b/dev/specs/002-user-facing-schema/spec.md @@ -0,0 +1,317 @@ +# Feature Specification: User-Facing Schema Separation + +**Feature Branch**: `user-facing-schema-infp-234` + +**Created**: 2026-07-01 + +**Status**: Draft + +**Input**: PRD `PRD-user-facing-schema-separation.md` + resolved field mapping `schema-field-classification.md` (repo root). Jira: INFP-234. + +## Overview + +Infrahub lets users define their own data models by submitting a schema. Today the +document that describes what a user may submit (the "user-facing schema") is a +direct dump of Infrahub's internal schema model. That has two consequences: it +advertises fields the user is not meant to set (so users set them and break their +schema), and it describes constrained fields as free-form text (so users and +automated tools cannot know the allowed values). This feature separates the +user-facing schema from the internal one so that what a user is shown is exactly +what a user may provide, with every constrained field publishing its allowed +values, and anything a user must not set rejected on submission. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Author a valid schema unassisted, first try (Priority: P1) + +An automated agent (or a person using one) obtains the description of what a +schema may contain, produces a schema, and submits it. Because the description +lists exactly which fields exist and, for each constrained field, the exact set of +allowed values, the agent can produce a correct schema without trial and error. If +it does get something wrong, the rejection tells it precisely which field is at +fault so it can self-correct. + +**Why this priority**: This is the strategic driver — enabling reliable +schema generation (including by LLMs) is the reason the work was prioritised. +Everything else in the feature is in service of, or a by-product of, making the +authoring contract complete and correct. + +**Independent Test**: Fetch the published write-schema description, generate a +schema payload from it alone, submit it, and confirm it loads. Separately, submit +a payload containing a field the user is not allowed to set and confirm the +response names that field. Delivers value on its own even if nothing else ships. + +**Acceptance Scenarios**: + +1. **Given** the published description of a submittable schema, **When** an author + generates a schema payload using only that description and submits it, **Then** + the payload loads successfully because every field it used exists and every + constrained value it chose was within the published allowed set. +2. **Given** a schema payload that includes a field the user is not permitted to + set (e.g. `inherited`) and an unknown field, **When** it is submitted, **Then** + it is rejected with a field-level, machine-readable error that names each + offending field, and nothing is stored. +3. **Given** a schema payload that sets a constrained field to a value outside its + allowed set (e.g. an attribute `kind` that does not exist), **When** it is + submitted, **Then** it is rejected with an error identifying the field and the + invalid value. + +--- + +### User Story 2 - Validate a schema locally, with no server (Priority: P2) + +A developer or an automated pipeline checks a schema for correctness before any +server is involved — in CI, on a laptop, or inside an agent loop — using only the +client library. The library gives the same field-and-value verdict the server +would give, so a schema that passes locally is not rejected by the server for +field or allowed-value reasons. + +**Why this priority**: Independently shippable and independently valuable: it +shortens the authoring feedback loop and removes a network round-trip, and it is +what makes the P1 flow cheap to iterate on. It depends on the same contract as P1 +but delivers value even if the server-side rejection work were deferred. + +**Independent Test**: In an environment where only the client library is installed +(no server, no backend), validate a good schema (passes) and a bad one (fails +naming the field), and confirm the local verdict matches the server's for the same +payloads. + +**Acceptance Scenarios**: + +1. **Given** only the client library is installed and no server is running, + **When** a well-formed schema is validated locally, **Then** validation passes. +2. **Given** the same environment, **When** a schema containing a non-settable + field or an out-of-range constrained value is validated locally, **Then** + validation fails and names the offending field — matching what the server would + return for that payload. + +--- + +### User Story 3 - Stop being able to set fields that break Infrahub (Priority: P2) + +A human author maintaining a schema by hand no longer sees internal, system-managed +fields presented as things they can set. Fields the system derives (such as whether +an attribute was inherited from a generic) are visible when reading a schema back +but are refused on submission, so the author cannot accidentally corrupt their +model by setting them. + +**Why this priority**: Directly removes a known source of customer friction and +support load. It shares the same mechanism as P1 (the classification of every +field) but is framed around the human-authoring benefit. + +**Independent Test**: Read back an existing schema and confirm derived fields are +present; submit a schema that tries to set one of those derived fields and confirm +it is refused. + +**Acceptance Scenarios**: + +1. **Given** an existing schema, **When** it is read back, **Then** system-derived + fields (e.g. `inherited`, `used_by`) are present and clearly not settable. +2. **Given** a submission that sets a system-derived field, **When** it is + submitted, **Then** it is refused with a field-level error. + +--- + +### Edge Cases + +- **Read-modify-write round-trip**: A client reads a schema, edits one field, and + re-submits the whole document. The read document contains visible-but-not-settable + fields, so the re-submission is now rejected. This is an accepted behavioural + change this cycle; the convenience export that would make round-tripping painless + is explicitly deferred (see Out of Scope). Clients that round-trip must remove + non-settable fields before submitting, using the published write description to + know which those are. +- **Derived kind field**: The submittable schema derives an object's kind from its + namespace and name. This derivation must continue to work under the new write + model (the derived value is not something the user supplies). +- **Schema extensions**: A submission that extends existing objects (rather than + defining new ones) is subject to the same rejection rules as a submission that + defines new nodes or generics. +- **Reading historical schemas**: A previously stored schema may contain fields now + classified as read-only-to-user. Reading it back must still succeed (the read + view is a superset of the write view). +- **Version skew**: Local (client-side) validation may run against a different + schema version than the server currently has. The version identifier carried on + the submission is the compatibility anchor; local validation is advisory and the + server remains authoritative. +- **Unclassified new field**: A newly added schema field with no explicit + visibility must not leak to users — it defaults to internal (hidden) until + someone deliberately classifies it. + +## Requirements *(mandatory)* + +### Field visibility model + +Every field in the schema definitions is assigned one visibility level: + +- **write** — the user may set it; accepted on submission; also visible on read. +- **read** — visible when reading a schema back, but refused on submission. +- **internal** — never exposed to users at all. + +The levels are nested: `write ⊆ read ⊆ internal`. An unclassified field defaults to +**internal**. The complete, resolved per-field assignment is recorded in +`schema-field-classification.md` and is the authoritative mapping for this feature. +Notable resolved decisions carried into the requirements: + +- `state` (on nodes, attributes, relationships) is **write** — it is a legitimate + load directive (present/absent) that users submit, e.g. to remove an element. +- Node `hierarchy` and relationship `hierarchical` are **read** — both are derived + from hierarchy/generic inheritance, not authored by the user. +- Relationship `identifier` is **write** — auto-generated when omitted but a user + may set it to align both directions of a relationship. +- The read-only-to-user set is `inherited` (attributes and relationships), + `used_by` (generics), and the two derived hierarchy fields above. +- The only never-exposed field is the parent back-reference from an attribute or + relationship to its owning node (it is implied by nesting, never in a payload). +- Deprecated fields (`default_filter`, `display_labels`, and attribute + `regex`/`min_length`/`max_length`) remain **write** this cycle. + +### Functional Requirements + +- **FR-001**: The schema-generation process MUST produce three distinct model sets + from the single field-definition source: a **write** model (what a submission may + contain), a **read** model (what a read-back returns), and the existing internal + model. *Verify:* the generated output contains three model families and + regeneration produces byte-identical output (idempotent). +- **FR-002**: Every schema field MUST carry a visibility classification of + `write`, `read`, or `internal`; a field with no explicit classification MUST be + treated as `internal`. *Verify:* a field left unclassified appears only in the + internal model; classified fields appear in exactly the models their level + implies. +- **FR-003**: A submission MUST be rejected if it contains any field that is not + `write`-level (i.e. `read`, `internal`, or unknown), and the rejection MUST name + each offending field in a machine-readable form. Nothing from a rejected + submission is stored. *Verify:* a submission containing `inherited` plus an + unknown field is rejected and both field names appear in the response. +- **FR-004**: Every field whose allowed values are already known internally (an + enumeration or otherwise bounded set) MUST publish that allowed-value set in the + write and read models, across the node, generic, attribute, and relationship + families. *Verify:* the write model for attribute `kind` and relationship + `kind`/`cardinality` (and every other enumerated field) publishes its full + allowed-value set; an automated scan finds no field described as free-form text + where a bounded set is defined internally. +- **FR-005**: A submission that sets a `write`-level constrained field to a value + outside its published allowed set MUST be rejected, naming the field and the + invalid value. *Verify:* a submission with a non-existent attribute `kind` is + rejected identifying the field and value. +- **FR-006**: The read model MUST include `read`-level fields and MUST exclude + `internal` fields. *Verify:* a read-back returns `inherited`/`used_by`; it never + returns the internal parent back-reference or any unclassified field. +- **FR-007**: The write and read models MUST be usable by the client library with + no server and no backend package present, enabling fully local validation. + *Verify:* in an environment with only the client library installed, a schema can + be validated and the correct pass/fail verdict returned. +- **FR-008**: The server MUST validate submissions and produce read-backs using the + same models the client library uses — not a separate server-side copy — so the + local and server verdicts cannot diverge for field and allowed-value rules. + *Verify:* the same payload yields the same field/allowed-value verdict locally and + on the server; a change to the shared model changes both behaviours. +- **FR-009**: The client library's previous hand-maintained schema models MUST be + replaced by the generated write/read models, leaving no second, parallel + definition of the same concepts. *Verify:* the former hand-written model classes + are gone and the library's callers use the generated models. +- **FR-010**: Reading back a schema that was stored before this change — including + one that contains fields now classified `read` — MUST continue to succeed. + *Verify:* a stored schema containing a now-`read` field is read back without + error. +- **FR-011**: The change MUST ship with a changelog entry and an upgrade note that + document the stricter submission behaviour (previously-accepted non-settable + fields are now rejected) and tell clients how to produce a submittable payload + (strip non-settable fields using the published write description). *Verify:* a + changelog fragment exists for this change and the upgrade note names the affected + endpoint and the client-side mitigation. +- **FR-012**: The generated write and read models MUST be committed, version- + controlled artifacts shipped inside the published client library package (not + build-time-only), so a consumer installing only the library obtains them. + *Verify:* the client library's own checks confirm the generated models are present + and non-stale, and a consumer install exposes them without any generation step. + +### Key Entities + +- **Field visibility classification** *(new)*: metadata attached to every schema + field declaring its level (`write`/`read`/`internal`). It is the single control + that decides what users may set, see, or never see. Governance-relevant. +- **Write model** *(new, external)*: the exact shape of a valid submission; the + authoring contract. Hosted so the client library can use it without a server. +- **Read model** *(new, external)*: the shape returned when reading a schema back; + a superset of the write model that adds visible-but-not-settable fields. +- **Internal model** *(existing)*: the full model used inside the server; + unchanged in shape, now the source from which the two external models are derived. +- **Schema families** *(existing)*: node, generic, attribute, and relationship + definitions — each gains per-field classifications and each produces its own + write/read projection. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: The write model exposes zero fields the user is not allowed to set — + every field it contains is settable. +- **SC-002**: Zero constrained fields are described as free-form where a bounded + set of allowed values is already defined internally (100% of known allowed-value + sets are published). +- **SC-003**: 100% of submissions containing a non-settable or unknown field are + rejected with a field-level error that names the field; zero are silently + accepted or partially stored. +- **SC-004**: An author working only from the published write description produces + a valid, loadable schema across a representative benchmark set at or above a + target first-attempt success rate. *(Target rate to be set with product; see Open + Questions.)* +- **SC-005**: A schema can be validated with only the client library present (no + server), and the local verdict matches the server's for every field and + allowed-value rule across a shared test set. +- **SC-006**: Every schema previously loadable remains loadable, and every stored + schema remains readable — no regression in existing authoring or read-back flows + beyond the intended rejection of non-settable fields on submission. + +## Assumptions + +- The visibility levels are strictly nested (`write ⊆ read ⊆ internal`); there is + no field a user must submit but must not be able to read back. +- The dominant way users author schemas is by writing submittable (write-shaped) + documents directly; reading a full schema and re-submitting it unchanged is a + minority path (see the round-trip edge case and Out of Scope). +- The internally defined allowed-value sets are authoritative and complete at the + time the models are generated. +- The client library is already a runtime dependency of the server, so the server + can use the library-hosted models, and the generation process can target the + library. +- The resolved field classification in `schema-field-classification.md` is correct + for this cycle; the remaining ambiguous fields have been decided (see the field + visibility model above). + +## Out of Scope + +- A convenience export that returns a write-shaped schema (excluding + read-only-to-user fields and omitting defaulted values) to make read-modify-write + round-trips painless. Deferred — it needs value-provenance tracking. +- Making specific attribute kinds (computed attributes, number pools) read-only by + default — a defaults-and-conditional-validation problem tracked separately. +- Updating the separate schema-visualizer consumer to the new models — assessed + after these models land. + +## Dependencies & Governance + +- **API / public interface change** — the submission endpoint becomes stricter + (breaking for payloads that carry non-settable fields) and the read-back shape + changes. Requires the "ask first" governance discussion before implementation. + Must ship with a changelog fragment and an upgrade note (FR-011). +- **Generated files + client-library changes** — regeneration produces new/changed + generated models in both the backend and the client library; both must be + regenerated and committed together, and CI must validate them on both sides. The + client-library models are committed, shipped artifacts (FR-012). +- **Server/client release compatibility** — server and client library now ship the + same generated contract; they must be released compatibly. The `version` field on + a submission is the compatibility anchor; client-side local validation is advisory + and the server remains authoritative on skew. +- **`id`-driven mutations** — because object `id` is user-settable (to rename/delete + an existing object), implementation MUST confirm existing authorization and branch + scoping prevent an `id` in a payload from targeting an object the caller may not + modify; this MUST be covered by a test. +- **No database, dependency, or authentication changes** (the authorization check + above reuses existing controls; it introduces no new auth model). + +## Open Questions + +- [NEEDS CLARIFICATION: SC-004 target first-attempt success rate and the benchmark + task set used to measure it — needs product input.] diff --git a/dev/specs/002-user-facing-schema/tasks.md b/dev/specs/002-user-facing-schema/tasks.md new file mode 100644 index 00000000000..0b6d51490df --- /dev/null +++ b/dev/specs/002-user-facing-schema/tasks.md @@ -0,0 +1,163 @@ +--- +description: "Task list for User-Facing Schema Separation (INFP-234)" +--- + +# Tasks: User-Facing Schema Separation + +**Input**: Design documents from `specs/002-user-facing-schema/` +**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md +**Tests**: INCLUDED — the spec's Testing Decisions and the constitution's Test Discipline require them. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: US1 / US2 / US3 (maps to spec user stories) +- Exact file paths are given per task. + +--- + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Make the SDK submodule available and understand what it currently ships. + +- [X] T001 Check out and sync the SDK submodule: `git submodule update --init python_sdk` then `uv sync --all-groups` (the write/read models will be generated into it). +- [X] T002 Audit the SDK's existing hand-written schema models under `python_sdk/infrahub_sdk/schema/` (e.g. `SchemaRootAPI`, `NodeSchemaAPI`, `GenericSchemaAPI`, `RelationshipSchemaAPI`, `AttributeSchemaAPI`): inventory their fields, `model_config`, and in-SDK consumers; record the mapping to the planned generated write/read models in `specs/002-user-facing-schema/research.md` (append an "SDK audit" section). +- [X] T003 [P] Confirm the Towncrier changelog setup and fragment types in `changelog/` so the breaking-change fragment (T027) uses a valid type. + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: The classification axis and the generator changes that every story depends on. + +**⚠️ CRITICAL**: No user story work can begin until this phase is complete. + +- [X] T004 Add a `visibility` axis to the schema-field metadata in `backend/infrahub/core/schema/definitions/internal.py`: extend `ExtraField` (TypedDict) with a `visibility` key backed by a new ordinal enum (`internal < read < write`, default `internal`) added to `backend/infrahub/core/constants` (alongside `UpdateSupport`); update `SchemaAttribute`/`SchemaRelationship` to carry it. +- [X] T005 Set `visibility` on every field in `backend/infrahub/core/schema/definitions/internal.py` (base_node, node, generic, attribute, relationship, and structural relationships) per the resolved mapping in `schema-field-classification.md` (read: `inherited`, `used_by`, node `hierarchy`, relationship `hierarchical`; internal: attribute/relationship `node` back-reference; everything else write). +- [X] T006 Extend the generator to emit write/read variants: parameterize `backend/templates/generate_schema.j2` (and its per-schema import includes) plus `tasks/backend.py::_generate_schemas` to render each family in `write` and `read` variants by filtering fields on `visibility` (reuse `without_duplicates` per variant); leave the existing internal variant unchanged. NOTE: implemented via a dedicated self-contained template `generate_schema_sdk.j2` + `_generate_schemas_sdk` rather than branching the shared internal template, to guarantee the internal variant stays byte-identical. +- [X] T007 In the generator template/logic, propagate allowed-value sets into write/read fields (currently dropped): render enum-class-backed fields (`branch`, relationship `kind`/`cardinality`/`direction`/`on_delete`, `allow_override`, `display`, `state`) as their enum type, and list-backed sets (attribute `kind` → `ATTRIBUTE_KIND_LABELS`) as `Literal[...]`/`json_schema_extra` enum, so the emitted JSON-schema is complete. NOTE: all constrained fields (including enum-class-backed) render as `Literal[...]` of their allowed values in the SDK models so they are self-contained (no backend enum imports); the internal variant is unchanged. +- [X] T008 Point write/read generation output at `python_sdk/infrahub_sdk/schema/` as committed artifacts (mirror the existing `protocols.py`→SDK generation in `tasks/backend.py`); keep the internal variant writing to `backend/infrahub/core/schema/generated/`. NOTE: generated into a fresh `python_sdk/infrahub_sdk/schema/generated/` subpackage (write.py/read.py) so the hand-written main.py is untouched this chunk. +- [X] T009 Run `uv run invoke backend.generate` and `uv run invoke schema.generate-jsonschema`; verify idempotency (`uv run invoke backend.generate` again → `git diff --exit-code` clean) and commit the generated write/read models in the SDK. + +**Checkpoint**: three model families generate from one source; write/read live in the SDK with enums; regeneration is stable. + +--- + +## Phase 3: User Story 1 - Agent authors a valid schema unassisted (Priority: P1) 🎯 MVP + +**Goal**: `POST /api/schema/load` accepts exactly the write model and rejects anything else with a field-level error; the published write JSON-schema is complete (enums present). + +**Independent Test**: Fetch the generated write JSON-schema, build a payload from it, load it successfully; submit a payload with a non-settable/unknown field or out-of-enum value and get a field-level rejection. + +### Tests for User Story 1 ⚠️ (write first, ensure they fail) + +- [X] T010 [P] [US1] Functional test in `backend/tests/functional/api/test_load_schema.py`: a payload carrying `inherited` (read-level) plus an unknown field is rejected, and the error names each offending field. +- [X] T011 [P] [US1] Functional test in `backend/tests/functional/api/test_load_schema.py`: a payload setting attribute `kind` to a non-existent value is rejected, naming the field and the invalid value. +- [X] T012 [P] [US1] Unit test in `backend/tests/unit/core/schema/test_generated_visibility.py`: the generated write model for each family exposes zero read/internal fields, and a scan finds no bare-`str`/`int` field where an internal enum/`Literal` is defined (SC-001/SC-002). + +### Implementation for User Story 1 + +- [X] T013 [US1] In `backend/infrahub/api/schema.py`, make the load path validate against the SDK write model (replace the `SchemaLoadAPI(SchemaRoot)` derivation with the generated SDK write model); retain `extra="forbid"` so non-write fields are rejected with field-level messages. +- [X] T014 [US1] Ensure the `kind`-from-`namespace`+`name` injection (`APISchemaMixin.set_kind`, `mode="before"`) remains compatible with the write model in `backend/infrahub/api/schema.py`. +- [X] T015 [US1] Ensure allowed-value validation is enforced by the write model itself (not only the internal-load `field_validator` in `backend/infrahub/core/schema/attribute_schema.py`), so out-of-enum values are rejected at the boundary. +- [X] T016 [US1] Regenerate and confirm the write JSON-schema (`schema/openapi.json` + the node-schema export) reflects the complete contract; commit. + +**Checkpoint**: an agent can author a valid schema from the write contract alone; invalid submissions are rejected clearly. + +--- + +## Phase 4: User Story 2 - Offline schema validation (Priority: P2) + +**Goal**: The SDK validates a schema against the write model with no server, and the verdict matches the server's; the SDK's hand-written models are gone. + +**Independent Test**: In an SDK-only environment, validate a good schema (passes) and a bad one (fails naming the field); confirm parity with the server for the same payloads. + +### Tests for User Story 2 ⚠️ + +- [X] T017 [P] [US2] SDK offline test in `python_sdk/tests/unit/test_schema_offline_validation.py`: with only the SDK installed, a valid payload passes and a payload with a non-settable/out-of-enum field fails naming the field. +- [X] T018 [P] [US2] Parity contract test in `backend/tests/functional/api/test_load_schema.py`: the same payload yields the same field/enum verdict via SDK offline validation and via `POST /api/schema/load`. + +### Implementation for User Story 2 + +- [X] T019 [US2] Expose an SDK offline-validation entry point in `python_sdk/infrahub_sdk/schema/` that validates a payload against the generated write model and returns a field-level verdict. NOTE: `validate_schema()` in `python_sdk/infrahub_sdk/schema/validate.py` (re-exported from `infrahub_sdk.schema`); the backend `POST /api/schema/load` boundary was repointed to call this same function, single-sourcing the write-validation logic and guaranteeing parity (T018). +- [ ] T020 [US2] ⚠️ PARTIAL — Remove the SDK's hand-written schema models (from T002 audit) and repoint in-SDK consumers to the generated write/read models; keep public import paths stable where feasible. DONE: consolidated the WRITE-validation logic — the generated write models are now the single source for the load boundary (SDK `validate_schema` + backend, backend's duplicated validator removed). NOT DONE: the hand-written models in `main.py` (`SchemaRoot`/`NodeSchema`/`GenericSchema`/`AttributeSchema`/`RelationshipSchema` + all `*API` read models with ~15 behavior methods, enums, `BranchSchema`) remain, and the 22 in-SDK consumers are not repointed. Reason: the generated read models lack `hash` and carry a different field set/typing (`Literal` vs enum classes, `computed_attribute`/`parameters`/`deprecation`/`display`/`common_parent`/`common_relatives`, `min_count`/`max_count` as required int) than the hand-written read models; a naive rebase changes serialization shape and breaks export/protocols-generator/node consumers and the SDK suite. Deferred to keep the SDK suite green (HARD REQUIREMENT); the behavior-subclass-over-generated-data-model refactor needs its own chunk to reconcile the field/typing deltas. +- [X] T021 [US2] Add an SDK CI check (in the SDK's own test/CI config) that the generated write/read models are present and non-stale (FR-012), matching the backend drift check. NOTE: `python_sdk/tests/unit/test_schema_generated_models.py` verifies presence + the do-not-edit header + the write(`extra=forbid`)/read-superset invariants — the strongest standalone drift guard the SDK repo can run (it cannot regenerate from the backend); full regeneration drift stays enforced by the monorepo's generated-file CI. + +**Checkpoint**: offline validation works from the SDK alone with server parity; no parallel hand-written models remain. + +--- + +## Phase 5: User Story 3 - Stop advertising non-settable fields; correct read-back (Priority: P2) + +**Goal**: `GET /api/schema` serialises via the read model — includes read-level fields, excludes internal — and historical schemas still read back; `id`-driven mutations remain authorised. + +**Independent Test**: Read a schema back and confirm `inherited`/`used_by` present and internal back-reference absent; read back a pre-existing stored schema successfully. + +### Tests for User Story 3 ⚠️ + +- [X] T022 [P] [US3] Component test in `backend/tests/component/api/test_40_schema.py`: `GET /api/schema` returns `inherited`/`used_by` and never returns the internal parent back-reference or unclassified fields (FR-005/FR-006). +- [X] T023 [P] [US3] Functional test in `backend/tests/functional/api/test_load_schema.py`: a stored schema containing a now-`read` field reads back without error (FR-010). +- [X] T024 [P] [US3] Test in `backend/tests/functional/api/test_load_schema.py`: submitting an `id` that targets an existing object honours existing authorization and branch scoping (cannot rename/delete an object the caller may not modify) (R1). + +### Implementation for User Story 3 + +- [X] T025 [US3] GET `/api/schema` serialises consistently with the generated read model. Chose option (b): the existing `APINodeSchema`/`APIGenericSchema`/`SchemaReadAPI` serialisation is PRESERVED (no risky rebase) and its visibility is now enforced against the generated read model by the T022 component test. Verified the backend internal Pydantic schema models have an identical field set to the generated read models (write+read fields, no internal-only Pydantic field — the parent back-reference is a meta-relationship, not a serialised field), so the response already carries every read-level field, excludes internal, and adds only `hash`/`kind`. No `hash` was added to read generation; `hash`/`kind` remain response-level additions. +- [X] T026 [US3] Confirmed the generated read model includes read-level fields (`inherited`, `used_by`, node `hierarchy`, relationship `hierarchical`) and excludes internal; a field-set comparison against the internal models showed zero read-only/internal-only deltas, so no change to the `read`-variant filter in `tasks/backend.py`/template was needed. + +**Checkpoint**: read-back is correct and backward-compatible; write and read models are both wired to the API. + +--- + +## Phase 6: Polish & Cross-Cutting Concerns + +- [X] T027 [P] Add a Towncrier changelog fragment in `changelog/` describing the breaking `POST /api/schema/load` behaviour (non-settable fields now rejected) (FR-011). +- [X] T028 [P] Write an upgrade/migration note (in `docs/` upgrade guide) documenting the stricter load behaviour and how clients strip non-settable fields against the published write schema (FR-011). +- [X] T029 Run the full `specs/002-user-facing-schema/quickstart.md` validation end-to-end. +- [X] T030 [P] Run `uv run invoke backend.generate`, `schema.generate-jsonschema`, and `docs.generate`; run `/pre-ci` and confirm the generated-file/`docs.validate` CI checks pass on both backend and SDK. +- [X] T031 [P] Update backend schema architecture notes in `dev/knowledge/backend/` to describe the write/read/internal model layering and the backend→SDK model dependency. + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: no dependencies. T002 audit informs T020. +- **Foundational (Phase 2)**: depends on Setup. **Blocks all user stories.** T004→T005→T006→T007→T008→T009 are largely sequential (each builds on the prior); T004 and T005 touch the same file (internal.py) so are not parallel. +- **US1 (Phase 3)**: depends on Foundational (needs the generated write model + enums). +- **US2 (Phase 4)**: depends on Foundational; T020 also depends on T002 (audit) and T013 (backend no longer needs old SDK models). Independently testable once the write model exists. +- **US3 (Phase 5)**: depends on Foundational (needs the read model). Independent of US1/US2. +- **Polish (Phase 6)**: after the desired stories are complete. + +### Within Each User Story + +- Tests first (must fail), then implementation. +- Generation/model changes before API wiring; API wiring before serialization polish. + +### Parallel Opportunities + +- T003 can run alongside T001/T002. +- All `[P]` test tasks within a story (T010–T012, T017–T018, T022–T024) can run in parallel. +- US1 and US3 can proceed in parallel after Phase 2 (different endpoints/paths); US2 can start once the write model exists but T020 should follow T013. +- Polish tasks T027/T028/T030/T031 are parallelizable. + +--- + +## Implementation Strategy + +### MVP First (User Story 1) + +1. Phase 1 Setup → 2. Phase 2 Foundational (CRITICAL) → 3. Phase 3 US1 → 4. STOP & validate the P1 authoring flow → deploy/demo. + +### Incremental Delivery + +Foundation → US1 (agent authoring, MVP) → US2 (offline validation) → US3 (read-back correctness) → Polish. Each story is independently testable and adds value without breaking prior ones. + +--- + +## Notes + +- Governance: this feature crosses the API-change and generated-files/submodule gates — the "ask first" discussion (recorded in the spec) must be settled before starting Phase 2/3 implementation. +- Do not hand-edit generated files (`backend/infrahub/core/schema/generated/`, generated SDK models); always regenerate via `uv run invoke backend.generate`. +- Commit after each task or logical group; regenerate + validate before pushing (`/pre-ci`). +- Open question (non-blocking): SC-004 benchmark target rate — set with product before measuring SC-004. diff --git a/docs/docs/release-notes/deprecation-guides/schema-load-write-contract.mdx b/docs/docs/release-notes/deprecation-guides/schema-load-write-contract.mdx new file mode 100644 index 00000000000..93a21d5ea54 --- /dev/null +++ b/docs/docs/release-notes/deprecation-guides/schema-load-write-contract.mdx @@ -0,0 +1,84 @@ +# Stricter schema-load validation + +Infrahub now validates every schema submitted to `POST /api/schema/load` against a +user-facing **write** contract and rejects any field that a user is not allowed to set. +Payloads that earlier versions silently accepted — because they echoed back fields that +Infrahub itself derives or computes — are now rejected with a field-level error. + +This guide explains what changed, who is affected, and how to make an existing payload +submittable again. + +:::warning +This is a breaking change to the load endpoint. A payload that loaded successfully before +may now be rejected. `GET /api/schema` is unchanged, so the most common way to hit this is +to read a schema back, edit it, and load it again without stripping the read-only fields. +::: + +## What changed + +Schema fields are now classified by who may see or set them: + +- **write** — fields a user may submit (for example `name`, `namespace`, `attributes`, + `relationships`, and each attribute's or relationship's settable properties). +- **read** — fields Infrahub computes and returns on `GET /api/schema`, but that a user may + not set. These include `inherited`, `used_by`, `hierarchy`, and the derived `kind` on a + node or generic. +- **internal** — fields used only inside the backend and never exposed. + +`POST /api/schema/load` validates each submitted node and generic against the write contract. +It rejects, with a field-level message naming the offending field: + +- any **read-only** field (`inherited`, `used_by`, `hierarchy`, derived `kind`), +- any **unknown** field, and +- any **constrained** field set outside its allowed values — for example an attribute + `kind` or a relationship `cardinality` / `kind`. + +`GET /api/schema` is unchanged: it still returns the read-only fields (`inherited`, +`used_by`, and so on). Only submission is stricter. + +## Who is affected + +You are affected if you submit schemas to `POST /api/schema/load` (directly, through +`infrahubctl schema load`, or through the Python SDK) and your payload includes fields that +Infrahub derives. The classic case is a round-trip: reading a schema with `GET /api/schema`, +modifying it, and loading it back without removing the read-only fields it contained. + +You are not affected if your schema files only ever contained the fields you author by hand +(the write fields); those payloads keep loading unchanged. + +## How to fix a rejected payload + +The write contract is published as a committed model inside the Python SDK, in +`infrahub_sdk.schema.generated.write`. The SDK exposes an offline validator that reproduces +the server's verdict without a running server, so you can check and correct a payload before +submitting it. + +```python +from infrahub_sdk.schema.validate import validate_schema + +# schema is your schema-root payload: {"version": "1.0", "nodes": [...], "generics": [...]} +result = validate_schema(schema) +if not result.valid: + for message in result.messages: + print(message) # e.g. "nodes[0].inherited: Extra inputs are not permitted" +``` + +Each message names the field path (for example `nodes[0].inherited` or +`nodes[0].attributes[1].kind`) so you can locate and remove the offending value. + +To make a payload submittable: + +1. **Remove the read-only fields** from every node and generic — `inherited`, `used_by`, + `hierarchy`, and the derived `kind`. Do not set them; Infrahub derives them. +2. **Remove any unknown fields** the validator reports. +3. **Correct constrained values** the validator reports as out of range (for example an + attribute `kind` that is not one of the allowed kinds). +4. Re-run `validate_schema()` until it returns `valid = True`, then load. + +Because the write model is versioned and shipped inside the SDK package, installing the SDK +that matches your Infrahub version gives you the exact contract that server enforces. + +## Related resources + +- [Create and load a schema](../../schema/create-and-load) — the schema loading guide +- [Node schema reference](../../reference/schema/node) — the schema field reference diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 2bf8ff50203..ecdaf604152 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -789,6 +789,7 @@ const sidebars: SidebarsConfig = { }, items: [ 'release-notes/deprecation-guides/display_labels', + 'release-notes/deprecation-guides/schema-load-write-contract', 'release-notes/deprecation-guides/sso-account-name-fallback', ], }, diff --git a/frontend/app/.betterer.results b/frontend/app/.betterer.results index 808dac48f67..38baf08fe8a 100644 --- a/frontend/app/.betterer.results +++ b/frontend/app/.betterer.results @@ -57,26 +57,11 @@ exports[`fix ts error`] = { [137, 22, 13, "tsc: Type \'Element | null | string\' is not assignable to type \'string | undefined\'.\\n Type \'null\' is not assignable to type \'string | undefined\'.", "1656119487"], [141, 22, 8, "tsc: Type \'Element | null | string\' is not assignable to type \'string | undefined\'.\\n Type \'null\' is not assignable to type \'string | undefined\'.", "288015442"] ], - "src/entities/events/ui/filters/global-branch-filter.tsx:3198570906": [ - [82, 14, 7, "tsc: Type \'{ label: string; name: string; }[]\' is not assignable to type \'\\"absent\\"; name: string; description?: string | null | null | null | null | undefined; color?: string | undefined; label?: string | undefined; state: \\"present\\" | undefined; }[] | { id?: string\'.\\n Property \'state\' is missing in type \'{ label: string; name: string; }\' but required in type \'\\"absent\\"; name: string; description?: string | null | null | null | null | undefined; color?: string | undefined; label?: string | undefined; state: \\"present\\" | undefined; } | { id?: string\'.", "3821181085"] - ], - "src/entities/events/ui/filters/global-events-filters.tsx:2842511618": [ - [21, 14, 7, "tsc: Type \'{ label: string; name: string; }[]\' is not assignable to type \'\\"absent\\"; name: string; description?: string | null | null | null | null | undefined; color?: string | undefined; label?: string | undefined; state: \\"present\\" | undefined; }[] | { id?: string\'.\\n Property \'state\' is missing in type \'{ label: string; name: string; }\' but required in type \'\\"absent\\"; name: string; description?: string | null | null | null | null | undefined; color?: string | undefined; label?: string | undefined; state: \\"present\\" | undefined; } | { id?: string\'.", "3821181085"], - [28, 12, 11, "tsc: Type \'{ kind: string; }\' is not assignable to type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'.\\n Type \'{ kind: string; }\' is missing 9 properties from type \'\\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"aware\\" | \\"extra\\"; } | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; choices?: { id?: string | undefined; color?: string | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'", "627546966"], - [33, 11, 16, "tsc: Property \'fieldSchema\' is missing in type \'{ name: string; label: string; }\' but required in type \'FilterTagProps\'.", "3434381446"], - [35, 11, 16, "tsc: Property \'fieldSchema\' is missing in type \'{ name: string; label: string; }\' but required in type \'FilterTagProps\'.", "3434381446"], - [40, 12, 11, "tsc: Type \'{ peer: string; }\' is not assignable to type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'.\\n Type \'{ peer: string; }\' is missing 12 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "627546966"], - [48, 12, 11, "tsc: Type \'{ kind: string; }\' is not assignable to type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'.\\n Type \'{ kind: string; }\' is missing 9 properties from type \'\\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"aware\\" | \\"extra\\"; } | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; choices?: { id?: string | undefined; color?: string | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'", "627546966"], - [56, 12, 11, "tsc: Type \'{ kind: string; }\' is not assignable to type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'.\\n Type \'{ kind: string; }\' is missing 9 properties from type \'\\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"aware\\" | \\"extra\\"; } | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; choices?: { id?: string | undefined; color?: string | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'", "627546966"] - ], - "src/entities/events/ui/filters/global-filter.tsx:2760934323": [ + "src/entities/events/ui/filters/global-filter.tsx:936359747": [ [53, 45, 5, "tsc: Type \'ReactNode\' is not assignable to type \'string\'.\\n Type \'undefined\' is not assignable to type \'string\'.", "173467459"] ], - "src/entities/events/ui/filters/global-kind-filter-form.tsx:2616398323": [ - [54, 52, 11, "tsc: Type \'{ peer: string; }\' is not assignable to type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'.\\n Type \'{ peer: string; }\' is missing 12 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "627546966"] - ], - "src/entities/events/ui/filters/global-kind-filter.tsx:3031218617": [ - [40, 45, 5, "tsc: Type \'ReactNode\' is not assignable to type \'string\'.\\n Type \'undefined\' is not assignable to type \'string\'.", "173467459"] + "src/entities/events/ui/filters/global-kind-filter.tsx:1514088599": [ + [38, 45, 5, "tsc: Type \'ReactNode\' is not assignable to type \'string\'.\\n Type \'undefined\' is not assignable to type \'string\'.", "173467459"] ], "src/entities/ipam/ip-addresses/ui/ip-address-table.tsx:3031736542": [ [45, 8, 7, "tsc: Type \'ColumnDef[]\' is not assignable to type \'ColumnDef[]\'.\\n Type \'ColumnDef\' is not assignable to type \'ColumnDef\'.\\n Type \'ColumnDefBase & StringHeaderIdentifier\' is not assignable to type \'ColumnDef\'.\\n Type \'ColumnDefBase & StringHeaderIdentifier\' is not assignable to type \'AccessorFnColumnDefBase | IpAddressAvailableNode, unknown> & IdIdentifier & StringHeaderIdentifier\' but required in type \'AccessorFnColumnDefBase\'.", "3718923584"] @@ -87,17 +72,14 @@ exports[`fix ts error`] = { "src/entities/navigation/ui/search-anywhere/search-nodes.tsx:1865424694": [ [127, 62, 4, "tsc: Property \'node\' does not exist on type \'NodeAttributeWithMetadata | NodeRelationshipManyWithMetadata | NodeRelationshipOneWithMetadata | string | string[]\'.\\n Property \'node\' does not exist on type \'string\'.", "2087865285"], [136, 16, 5, "tsc: Type \'null | string | undefined\' is not assignable to type \'string\'.\\n Type \'undefined\' is not assignable to type \'string\'.", "183222373"], - [137, 29, 4, "tsc: Property \'kind\' does not exist on type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; isAttribute: boolean; } | \\"extra\\"; isRelationship: boolean; paginated: boolean; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { label: string; name: string; }\'.\\n Property \'kind\' does not exist on type \'{ label: string; name: string; }\'.", "2088042925"], + [137, 29, 4, "tsc: Property \'kind\' does not exist on type \'\\"Attribute\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Color\\" | \\"Component\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Email\\" | \\"File\\" | \\"Group\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"ID\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Password\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; isRelationship: boolean; paginated: boolean; } | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; isAttribute: boolean; } | undefined; isAttribute: boolean; } | undefined; isAttribute: boolean; } | undefined; isAttribute: boolean; } | undefined; isAttribute: boolean; } | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; state: \\"present\\" | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { label: string; name: string; }\'.\\n Property \'kind\' does not exist on type \'{ label: string; name: string; }\'.", "2088042925"], [138, 16, 5, "tsc: Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'boolean | null; label: string; color: string; } | null; } | number | { edges: { node: NodeCore; }[]; } | { node: NodeCore; } | { value: string | { value: string\'.\\n Type \'undefined\' is not assignable to type \'boolean | null; label: string; color: string; } | null; } | number | { edges: { node: NodeCore; }[]; } | { node: NodeCore; } | { value: string | { value: string\'.", "189936718"] ], "src/entities/nodes/convert/ui/convert-form.tsx:1982113281": [ [41, 6, 8, "tsc: Type \'{}\' is not assignable to type \'Record\'.\\n Index signature for type \'string\' is missing in type \'{}\'.", "1301887866"] ], - "src/entities/nodes/object/ui/filters/dynamic-filter-input.tsx:1945941723": [ - [118, 25, 9, "tsc: Argument of type \'\\"NodeKind\\"\' is not assignable to parameter of type \'never\'.", "2512581423"] - ], - "src/entities/nodes/object/ui/filters/kind-combobox-list.tsx:2661968571": [ - [19, 8, 8, "tsc: Type \'(null | string | undefined)[]\' is not assignable to type \'string[]\'.\\n Type \'string | null | undefined\' is not assignable to type \'string\'.\\n Type \'undefined\' is not assignable to type \'string\'.", "3441362927"] + "src/entities/nodes/object/ui/filters/dynamic-filter-input.tsx:2811726414": [ + [114, 25, 9, "tsc: Argument of type \'\\"NodeKind\\"\' is not assignable to parameter of type \'never\'.", "2512581423"] ], "src/entities/nodes/object/ui/object-details/action-buttons/details-buttons.tsx:1531921325": [ [54, 21, 25, "tsc: \'objectDetailsData.targets\' is possibly \'null\' or \'undefined\'.", "1788660526"], @@ -111,13 +93,13 @@ exports[`fix ts error`] = { [154, 75, 10, "tsc: Property \'properties\' does not exist on type \'NodeAttributeWithMetadata | NodeRelationshipManyWithMetadata | NodeRelationshipOneWithMetadata | string | string[]\'.\\n Property \'properties\' does not exist on type \'string\'.", "408285988"] ], "src/entities/nodes/object/ui/object-details/object-data-display/object-relationship-row.test.tsx:3396911868": [ - [81, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], - [98, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], - [114, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; onClickMetadata: Mock; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], - [135, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; onClickMetadata: Mock; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], - [162, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], - [222, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipManyWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], - [237, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipManyWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"] + [81, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], + [98, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], + [114, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; onClickMetadata: Mock; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], + [135, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; onClickMetadata: Mock; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], + [162, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipOneWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], + [222, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipManyWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"], + [237, 9, 21, "tsc: Property \'objectKind\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; relationshipData: NodeRelationshipManyWithMetadata; permission: Permission; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { relationshipSchema: { id?: string\' but required in type \'ObjectRelationshipRowProps\'.", "417292446"] ], "src/entities/nodes/object/ui/object-relationship-list.tsx:54284658": [ [38, 54, 5, "tsc: Property \'items\' does not exist on type \'NodeObject[]\'.", "179721187"] @@ -152,7 +134,7 @@ exports[`fix ts error`] = { [93, 52, 4, "tsc: Binding element \'node\' implicitly has an \'any\' type.", "2087865285"] ], "src/entities/repository/ui/repository-objects-manager.tsx:3064139168": [ - [40, 6, 18, "tsc: Type \'\\"Attribute\\" | \\"Attribute\\" | \\"Attribute\\" | \\"Attribute\\" | \\"Component\\" | \\"Component\\" | \\"Component\\" | \\"Component\\" | \\"Group\\" | \\"Group\\" | \\"Group\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Parent\\" | \\"Parent\\" | \\"Parent\\" | \\"Profile\\" | \\"Profile\\" | \\"Profile\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; min_value?: number | \\"absent\\"; min_value?: number | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"absent\\"; } | \\"absent\\"; } | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"cascade\\" | \\"cascade\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | \\"outbound\\" | \\"outbound\\" | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; attributes?: { id?: string | undefined; attributes?: { id?: string | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { id?: string | undefined; choices?: { id?: string | undefined; choices?: { id?: string | undefined; choices?: { id?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; deprecation?: string | undefined; deprecation?: string | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_label?: string | undefined; display_label?: string | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; documentation?: string | undefined; documentation?: string | undefined; documentation?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash: string; } | undefined; hash: string; } | undefined; hash: string; } | undefined; hash: string; } | undefined; hierarchical: boolean; generate_profile: boolean; used_by?: string[] | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; icon?: string | undefined; icon?: string | undefined; icon?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherit_from?: string[] | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; kind?: string | undefined; kind?: string | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; on_delete?: \\"no-action\\" | undefined; on_delete?: \\"no-action\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; restricted_namespaces?: string[] | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\' is not assignable to type \'ModelSchema\'.\\n Type \'null\' is not assignable to type \'ModelSchema\'.", "3283107600"] + [40, 6, 18, "tsc: Type \'\\"Attribute\\" | \\"Attribute\\" | \\"Attribute\\" | \\"Attribute\\" | \\"Bandwidth\\" | \\"Bandwidth\\" | \\"Bandwidth\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Boolean\\" | \\"Boolean\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Checkbox\\" | \\"Checkbox\\" | \\"Checkbox\\" | \\"Color\\" | \\"Color\\" | \\"Color\\" | \\"Color\\" | \\"Component\\" | \\"Component\\" | \\"Component\\" | \\"Component\\" | \\"DateTime\\" | \\"DateTime\\" | \\"DateTime\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Dropdown\\" | \\"Dropdown\\" | \\"Dropdown\\" | \\"Email\\" | \\"Email\\" | \\"Email\\" | \\"Email\\" | \\"File\\" | \\"File\\" | \\"File\\" | \\"File\\" | \\"Group\\" | \\"Group\\" | \\"Group\\" | \\"Group\\" | \\"HashedPassword\\" | \\"HashedPassword\\" | \\"HashedPassword\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"ID\\" | \\"ID\\" | \\"ID\\" | \\"ID\\" | \\"IPHost\\" | \\"IPHost\\" | \\"IPHost\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"IPNetwork\\" | \\"IPNetwork\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"JSON\\" | \\"JSON\\" | \\"JSON\\" | \\"MacAddress\\" | \\"MacAddress\\" | \\"MacAddress\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Parent\\" | \\"Parent\\" | \\"Parent\\" | \\"Password\\" | \\"Password\\" | \\"Password\\" | \\"Password\\" | \\"Profile\\" | \\"Profile\\" | \\"Profile\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"TextArea\\"; enum?: unknown[] | \\"TextArea\\"; enum?: unknown[] | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"absent\\"; on_delete?: \\"no-action\\" | \\"absent\\"; on_delete?: \\"no-action\\" | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"cascade\\" | \\"cascade\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | \\"outbound\\" | \\"outbound\\" | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_label?: string | undefined; display_label?: string | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; documentation?: string | undefined; documentation?: string | undefined; documentation?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash?: string | undefined; hash?: string | undefined; hash?: string | undefined; hash?: string | undefined; hierarchical: boolean; generate_profile: boolean; used_by?: string[] | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; icon?: string | undefined; icon?: string | undefined; icon?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherit_from?: string[] | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; kind: \\"Text\\" | undefined; name: string; kind: \\"Text\\" | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; namespace: string; description?: string | undefined; name: string; namespace: string; description?: string | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; readonly kind: string; } | undefined; readonly kind: string; } | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; restricted_namespaces?: string[] | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; })[] | undefined; })[] | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\' is not assignable to type \'ModelSchema\'.\\n Type \'null\' is not assignable to type \'ModelSchema\'.", "3283107600"] ], "src/entities/resource-manager/ui/number-pool-form.tsx:3337327002": [ [50, 39, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], @@ -172,7 +154,7 @@ exports[`fix ts error`] = { [41, 53, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], [42, 41, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], [51, 62, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record> | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record>\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'Pick\'.\\n Type \'undefined\' is not assignable to type \'Pick\'.", "3940586649"], - [131, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] + [131, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] ], "src/entities/role-manager/ui/account-group-form.tsx:1576275383": [ [50, 39, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], @@ -180,22 +162,22 @@ exports[`fix ts error`] = { [52, 41, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], [53, 51, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], [63, 62, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record> | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record>\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'Pick\'.\\n Type \'undefined\' is not assignable to type \'Pick\'.", "3940586649"], - [138, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"], - [149, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] + [138, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"], + [149, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] ], "src/entities/role-manager/ui/account-role-form.tsx:4112378484": [ [48, 39, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], [58, 62, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record> | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record>\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'Pick\'.\\n Type \'undefined\' is not assignable to type \'Pick\'.", "3940586649"], - [122, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] + [122, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] ], "src/entities/role-manager/ui/global-permissions-form.tsx:3521917364": [ [45, 43, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], [46, 47, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], [66, 62, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record> | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record>\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'Pick\'.\\n Type \'undefined\' is not assignable to type \'Pick\'.", "3940586649"], - [120, 8, 5, "tsc: Type \'\\"absent\\"; name: string; description?: string | null | null | null | null | undefined | undefined; color?: string | undefined; label?: string | undefined; state: \\"present\\" | undefined; }[] | { value: string; id?: string\' is not assignable to type \'DropdownOption[]\'.\\n Type \'undefined\' is not assignable to type \'DropdownOption[]\'.", "179721187"], + [120, 8, 5, "tsc: Type \'null | null | null | undefined | undefined; color?: string | undefined; label?: string | undefined; }[] | { value: string; name: string; description?: string\' is not assignable to type \'DropdownOption[]\'.\\n Type \'undefined\' is not assignable to type \'DropdownOption[]\'.", "179721187"], [127, 8, 11, "tsc: Type \'null | string | undefined\' is not assignable to type \'string | undefined\'.\\n Type \'null\' is not assignable to type \'string | undefined\'.", "1848305091"], [130, 8, 5, "tsc: Type \'{ value: number; label: string; }[]\' is not assignable to type \'DropdownOption[]\'.\\n Type \'{ value: number; label: string; }\' is not assignable to type \'DropdownOption\'.\\n Types of property \'value\' are incompatible.\\n Type \'number\' is not assignable to type \'string\'.", "179721187"], - [138, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] + [138, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] ], "src/entities/role-manager/ui/object-permissions-form.tsx:3921507247": [ [46, 49, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'NodeAttributeWithMetadata\'.\\n Type \'undefined\' is not assignable to type \'NodeAttributeWithMetadata\'.", "3940586649"], @@ -205,13 +187,13 @@ exports[`fix ts error`] = { [81, 62, 13, "tsc: Argument of type \'NodeFieldsWithMetadata | undefined\' is not assignable to parameter of type \'Record> | undefined\'.\\n Type \'NodeFieldsWithMetadata\' is not assignable to type \'Record>\'.\\n \'string\' index signatures are incompatible.\\n Type \'NodeAttributeWithMetadata | NodeCorePropertyValue | NodeRelationshipWithMetadata\' is not assignable to type \'Pick\'.\\n Type \'undefined\' is not assignable to type \'Pick\'.", "3940586649"], [143, 8, 11, "tsc: Type \'null | string | undefined\' is not assignable to type \'string | undefined\'.\\n Type \'null\' is not assignable to type \'string | undefined\'.", "1848305091"], [146, 8, 5, "tsc: Type \'{ value: number; label: string; }[]\' is not assignable to type \'DropdownOption[]\'.\\n Type \'{ value: number; label: string; }\' is not assignable to type \'DropdownOption\'.\\n Types of property \'value\' are incompatible.\\n Type \'number\' is not assignable to type \'string\'.", "179721187"], - [153, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] + [153, 8, 12, "tsc: Type \'{ name: string; peer: string; cardinality: \\"many\\"; }\' is missing 10 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'", "2930100897"] ], "src/entities/role-manager/ui/permission-combobox.tsx:4285110127": [ [95, 64, 9, "tsc: Type \'(relationship: PermissionNode) => void\' is not assignable to type \'(newObject: NodeCore) => void\'.\\n Types of parameters \'relationship\' and \'newObject\' are incompatible.\\n Property \'identifier\' is missing in type \'NodeCore\' but required in type \'PermissionNode\'.", "3172999"] ], - "src/entities/schema/ui/computed-attribute-display.tsx:1618543609": [ - [37, 14, 8, "tsc: Type \'{ title: string; fileName: string; data: string; }\' is not assignable to type \'IntrinsicAttributes & DataViewerProps\'.\\n Property \'fileName\' does not exist on type \'IntrinsicAttributes & DataViewerProps\'.", "3193632964"] + "src/entities/schema/ui/computed-attribute-display.tsx:4204959992": [ + [38, 14, 8, "tsc: Type \'{ title: string; fileName: string; data: string; }\' is not assignable to type \'IntrinsicAttributes & DataViewerProps\'.\\n Property \'fileName\' does not exist on type \'IntrinsicAttributes & DataViewerProps\'.", "3193632964"] ], "src/entities/tasks/ui/logs.tsx:3377694090": [ [49, 31, 4, "tsc: Type \'{ values: { severity: any; timestamp: Element; id: string; message: string; }; }[]\' is not assignable to type \'tRow[]\'.\\n Type \'{ values: { severity: any; timestamp: JSX.Element; id: string; message: string; }; }\' is not assignable to type \'tRow\'.\\n Types of property \'values\' are incompatible.\\n Type \'{ severity: any; timestamp: JSX.Element; id: string; message: string; }\' is not assignable to type \'Record\'.\\n Property \'timestamp\' is incompatible with index signature.\\n Type \'Element\' is not assignable to type \'string | number | tRowValue\'.", "2088305148"] @@ -283,7 +265,7 @@ exports[`fix ts error`] = { [5, 24, 5, "tsc: Type \'unknown\' is not assignable to type \'Error\'.", "165548477"] ], "src/shared/components/form/dynamic-form.tsx:3819858559": [ - [98, 14, 11, "tsc: Type \'\\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"any\\"; parameters?: { id?: string | \\"aware\\" | \\"aware\\" | \\"extra\\"; } | \\"extra\\"; } | \\"setValueAs\\" | \\"setValueAs\\" | \\"valueAsDate\\"> | \\"valueAsDate\\"> | \\"valueAsNumber\\" | \\"valueAsNumber\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; choices?: { id?: string | undefined; choices?: { id?: string | undefined; color?: string | undefined; color?: string | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; defaultValue?: FormAttributeValue | undefined; defaultValue?: FormAttributeValue | undefined; deprecation?: string | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; disabled?: boolean | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; isBulkUpdate?: boolean | undefined; isBulkUpdate?: boolean | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; placeholder?: string | undefined; name: string; placeholder?: string | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; pools?: NumberPool[] | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; regex?: string | undefined; rules?: Omit, \\"disabled\\" | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; unique?: boolean | undefined; unique?: boolean | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | undefined; }[] | { attribute?: { id?: string | { attribute?: { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\' is not assignable to type \'IntrinsicAttributes & NumberFieldProps\'.\\n Type \'\\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"any\\"; parameters?: { id?: string | \\"aware\\" | \\"extra\\"; } | \\"setValueAs\\" | \\"valueAsDate\\"> | \\"valueAsNumber\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; choices?: { id?: string | undefined; color?: string | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; defaultValue?: FormAttributeValue | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; excluded_values?: string | undefined; isBulkUpdate?: boolean | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; placeholder?: string | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; unique?: boolean | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { attribute?: { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\' is not assignable to type \'NumberFieldProps\'.\\n Types of property \'onChange\' are incompatible.\\n Type \'((value: FormFieldValue) => void) | undefined\' is not assignable to type \'ChangeEventHandler | undefined\'.\\n Type \'(value: FormFieldValue) => void\' is not assignable to type \'ChangeEventHandler\'.\\n Types of parameters \'value\' and \'event\' are incompatible.\\n Type \'ChangeEvent\' is not assignable to type \'FormFieldValue\'.", "588154884"], + [98, 14, 11, "tsc: Type \'\\"setValueAs\\" | \\"setValueAs\\" | \\"valueAsDate\\"> | \\"valueAsDate\\"> | \\"valueAsNumber\\" | \\"valueAsNumber\\" | undefined; defaultValue?: FormAttributeValue | undefined; defaultValue?: FormAttributeValue | undefined; description?: string | undefined; description?: string | undefined; disabled?: boolean | undefined; disabled?: boolean | undefined; isBulkUpdate?: boolean | undefined; isBulkUpdate?: boolean | undefined; label?: string | undefined; label?: string | undefined; name: string; placeholder?: string | undefined; name: string; placeholder?: string | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; pools?: NumberPool[] | undefined; rules?: Omit, \\"disabled\\" | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; shouldUnregister?: boolean | undefined; unique?: boolean | undefined; unique?: boolean | undefined; } | undefined; } | undefined; } | undefined; } | { attribute?: AttributeSchema | { attribute?: AttributeSchema\' is not assignable to type \'IntrinsicAttributes & NumberFieldProps\'.\\n Type \'\\"setValueAs\\" | \\"valueAsDate\\"> | \\"valueAsNumber\\" | undefined; defaultValue?: FormAttributeValue | undefined; description?: string | undefined; disabled?: boolean | undefined; isBulkUpdate?: boolean | undefined; label?: string | undefined; name: string; placeholder?: string | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; unique?: boolean | undefined; } | undefined; } | { attribute?: AttributeSchema\' is not assignable to type \'NumberFieldProps\'.\\n Types of property \'onChange\' are incompatible.\\n Type \'((value: FormFieldValue) => void) | undefined\' is not assignable to type \'ChangeEventHandler | undefined\'.\\n Type \'(value: FormFieldValue) => void\' is not assignable to type \'ChangeEventHandler\'.\\n Types of parameters \'value\' and \'event\' are incompatible.\\n Type \'ChangeEvent\' is not assignable to type \'FormFieldValue\'.", "588154884"], [148, 25, 5, "tsc: Argument of type \'DynamicKindFieldProps\' is not assignable to parameter of type \'never\'.", "187023499"] ], "src/shared/components/form/fields/color.field.tsx:220004711": [ @@ -305,22 +287,22 @@ exports[`fix ts error`] = { ], "src/shared/components/form/fields/peer.field.tsx:3794383852": [ [32, 4, 8, "tsc: Type \'NodeCore | NodeCore[] | null | undefined | { from_pool: { id: string; }; }\' is not assignable to type \'string\'.\\n Type \'undefined\' is not assignable to type \'string\'.", "4118035101"], - [33, 4, 12, "tsc: Type \'{ kind: string; }\' is not assignable to type \'ModelSchema\'.\\n Type \'{ kind: string; }\' is missing 5 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'", "2303311585"], + [33, 4, 12, "tsc: Type \'{ kind: string; }\' is not assignable to type \'ModelSchema\'.\\n Type \'{ kind: string; }\' is missing 4 properties from type \'\\"Attribute\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Color\\" | \\"Component\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Email\\" | \\"File\\" | \\"Group\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"ID\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Password\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\'", "2303311585"], [56, 64, 21, "tsc: Argument of type \'NodeCore[] | PoolSource | TemplateSource | null | null | null | undefined; display_label: string | undefined; value: { id: NodeCore | undefined; }; } | { from_pool: { id: string; }; } | { source: ProfileSource | { type: SourceType; } | { type: SourceType; }\' is not assignable to parameter of type \'FormRelationshipValue | undefined\'.\\n Type \'NodeCore[] | PoolSource | TemplateSource | null | null | null | undefined; display_label: string | undefined; value: { id: NodeCore | undefined; }; } | { from_pool: { id: string; }; } | { source: ProfileSource | { type: SourceType; } | { type: SourceType; }\' is not assignable to type \'EmptyFieldValue | RelationshipManyValueFromProfile | RelationshipManyValueFromTemplate | RelationshipManyValueFromUser | RelationshipOneValueFromProfile | RelationshipOneValueFromTemplate | RelationshipOneValueFromUser | RelationshipValueFromPool\'.\\n Type \'NodeCore[] | PoolSource | TemplateSource | null | null | null | undefined; display_label: string | undefined; value: { id: NodeCore | undefined; }; } | { from_pool: { id: string; }; } | { source: ProfileSource | { type: SourceType; } | { type: SourceType; }\' is not assignable to type \'RelationshipManyValueFromProfile\'.\\n Types of property \'source\' are incompatible.\\n Type \'PoolSource | ProfileSource | TemplateSource | null | undefined | { type: SourceType; } | { type: SourceType; }\' is not assignable to type \'ProfileSource\'.\\n Type \'undefined\' is not assignable to type \'ProfileSource\'.", "2333515987"], [63, 48, 13, "tsc: Property \'display_label\' does not exist on type \'NodeCore | { from_pool: { id: string; }; }\'.\\n Property \'display_label\' does not exist on type \'{ from_pool: { id: string; }; }\'.", "1907695430"], [84, 18, 4, "tsc: Type \'string | undefined\' is not assignable to type \'string\'.\\n Type \'undefined\' is not assignable to type \'string\'.", "2088092007"] ], "src/shared/components/form/fields/relationship-many.field.test.tsx:3958905593": [ - [48, 9, 21, "tsc: Property \'type\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; filterQuery?: Record | \\"valueAsNumber\\" | boolean | null | null | null | null | null | null | null | null | null | null | null | number | string[]> | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; description?: string | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; isBulkUpdate?: boolean | undefined; name: string; label?: string | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; options?: SelectOption[] | undefined; order_weight?: number | undefined; parent?: string | undefined; peer?: string | undefined; peerField?: string | undefined; placeholder?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; relationship: { id?: string | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; unique?: boolean | undefined; } | undefined; } | { defaultValue?: FormRelationshipValue\' but required in type \'DynamicRelationshipFieldProps\'.", "2975306552"], - [62, 9, 21, "tsc: Property \'type\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; filterQuery?: Record | \\"valueAsNumber\\" | boolean | null | null | null | null | null | null | null | null | null | null | null | number | string[]> | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; isBulkUpdate?: boolean | undefined; name: string; label?: string | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; options?: SelectOption[] | undefined; order_weight?: number | undefined; parent?: string | undefined; peer?: string | undefined; peerField?: string | undefined; placeholder?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; relationship: { id?: string | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; unique?: boolean | undefined; } | undefined; } | { description: string; defaultValue?: FormRelationshipValue\' but required in type \'DynamicRelationshipFieldProps\'.", "2975306552"], - [74, 9, 21, "tsc: Property \'type\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; filterQuery?: Record | \\"valueAsNumber\\" | boolean | null | null | null | null | null | null | null | null | null | null | null | number | string[]> | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; description?: string | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; isBulkUpdate?: boolean | undefined; name: string; label?: string | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; options?: SelectOption[] | undefined; order_weight?: number | undefined; parent?: string | undefined; peer?: string | undefined; peerField?: string | undefined; placeholder?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; relationship: { id?: string | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; } | undefined; } | { unique: true; defaultValue?: FormRelationshipValue\' but required in type \'DynamicRelationshipFieldProps\'.", "2975306552"], - [86, 9, 21, "tsc: Property \'type\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; filterQuery?: Record | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; description?: string | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; isBulkUpdate?: boolean | undefined; name: string; label?: string | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; options?: SelectOption[] | undefined; order_weight?: number | undefined; parent?: string | undefined; peer?: string | undefined; peerField?: string | undefined; placeholder?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; relationship: { id?: string | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; unique?: boolean | undefined; } | undefined; } | { rules: { required: true; }; defaultValue?: FormRelationshipValue\' but required in type \'DynamicRelationshipFieldProps\'.", "2975306552"] + [48, 9, 21, "tsc: Property \'type\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; filterQuery?: Record | \\"valueAsNumber\\" | boolean | null | null | null | null | null | null | null | null | null | null | null | number | string[]> | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; description?: string | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; isBulkUpdate?: boolean | undefined; name: string; label?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; optional: boolean; branch?: \\"local\\" | undefined; options?: SelectOption[] | undefined; order_weight?: number | undefined; parent?: string | undefined; peer?: string | undefined; peerField?: string | undefined; placeholder?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; relationship: { id?: string | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; unique?: boolean | undefined; } | undefined; } | { defaultValue?: FormRelationshipValue\' but required in type \'DynamicRelationshipFieldProps\'.", "2975306552"], + [62, 9, 21, "tsc: Property \'type\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; filterQuery?: Record | \\"valueAsNumber\\" | boolean | null | null | null | null | null | null | null | null | null | null | null | number | string[]> | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; isBulkUpdate?: boolean | undefined; name: string; label?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; optional: boolean; branch?: \\"local\\" | undefined; options?: SelectOption[] | undefined; order_weight?: number | undefined; parent?: string | undefined; peer?: string | undefined; peerField?: string | undefined; placeholder?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; relationship: { id?: string | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; unique?: boolean | undefined; } | undefined; } | { description: string; defaultValue?: FormRelationshipValue\' but required in type \'DynamicRelationshipFieldProps\'.", "2975306552"], + [74, 9, 21, "tsc: Property \'type\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; filterQuery?: Record | \\"valueAsNumber\\" | boolean | null | null | null | null | null | null | null | null | null | null | null | number | string[]> | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; description?: string | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; isBulkUpdate?: boolean | undefined; name: string; label?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; optional: boolean; branch?: \\"local\\" | undefined; options?: SelectOption[] | undefined; order_weight?: number | undefined; parent?: string | undefined; peer?: string | undefined; peerField?: string | undefined; placeholder?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; relationship: { id?: string | undefined; rules?: Omit, \\"disabled\\" | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; } | undefined; } | { unique: true; defaultValue?: FormRelationshipValue\' but required in type \'DynamicRelationshipFieldProps\'.", "2975306552"], + [86, 9, 21, "tsc: Property \'type\' is missing in type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }; filterQuery?: Record | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; description?: string | undefined; disabled?: boolean | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; isBulkUpdate?: boolean | undefined; name: string; label?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; onChange?: ((value: FormFieldValue) => void) | undefined; optional: boolean; branch?: \\"local\\" | undefined; options?: SelectOption[] | undefined; order_weight?: number | undefined; parent?: string | undefined; peer?: string | undefined; peerField?: string | undefined; placeholder?: string | undefined; pool?: { kind: string; defaultAllocatedObjectKind: string; fromPoolRelationshipName?: string | undefined; relationship: { id?: string | undefined; shouldUnregister?: boolean | undefined; state: \\"present\\" | undefined; unique?: boolean | undefined; } | undefined; } | { rules: { required: true; }; defaultValue?: FormRelationshipValue\' but required in type \'DynamicRelationshipFieldProps\'.", "2975306552"] ], "src/shared/components/form/fields/relationships/generic-relationship.field.tsx:2768453478": [ - [185, 56, 4, "tsc: Property \'name\' does not exist on type \'\\"\\" | \\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'.\\n Property \'name\' does not exist on type \'\\"\\"\'.", "2087876002"] + [185, 56, 4, "tsc: Property \'name\' does not exist on type \'\\"\\" | \\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"cascade\\" | \\"extra\\"; } | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; cardinality: \\"one\\" | undefined; common_relatives?: string[] | undefined; description?: string | undefined; display: \\"default\\" | undefined; identifier?: string | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_weight?: number | undefined; state: \\"present\\" | { id?: string\'.\\n Property \'name\' does not exist on type \'\\"\\"\'.", "2087876002"] ], "src/shared/components/form/file-with-profile-form.tsx:3973907063": [ - [13, 8, 6, "tsc: Type \'\\"Attribute\\" | \\"Attribute\\" | \\"Component\\" | \\"Component\\" | \\"Group\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Parent\\" | \\"Profile\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { id?: string | undefined; choices?: { id?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; documentation?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash: string; } | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; icon?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\' is not assignable to type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'.\\n Type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\' is missing 2 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'", "2166186324"] + [13, 8, 6, "tsc: Type \'\\"Attribute\\" | \\"Attribute\\" | \\"Bandwidth\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Checkbox\\" | \\"Color\\" | \\"Color\\" | \\"Component\\" | \\"Component\\" | \\"DateTime\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Dropdown\\" | \\"Email\\" | \\"Email\\" | \\"File\\" | \\"File\\" | \\"Group\\" | \\"Group\\" | \\"HashedPassword\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"ID\\" | \\"ID\\" | \\"IPHost\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"JSON\\" | \\"MacAddress\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Parent\\" | \\"Password\\" | \\"Password\\" | \\"Profile\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; documentation?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; icon?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\' is not assignable to type \'\\"Attribute\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Color\\" | \\"Component\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Email\\" | \\"File\\" | \\"Group\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"ID\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Password\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\'.\\n Type \'\\"Attribute\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Color\\" | \\"Component\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Email\\" | \\"File\\" | \\"Group\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"ID\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Password\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\' is missing 2 properties from type \'\\"Attribute\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Color\\" | \\"Component\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Email\\" | \\"File\\" | \\"Group\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"ID\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Password\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\'", "2166186324"] ], "src/shared/components/form/generic-object-form.tsx:1723799544": [ [14, 4, 69, "tsc: Argument of type \'null | string | undefined\' is not assignable to parameter of type \'(() => string | null | null) | string\'.\\n Type \'undefined\' is not assignable to type \'(() => string | null | null) | string\'.", "2587726796"] @@ -330,10 +312,10 @@ exports[`fix ts error`] = { [87, 8, 5, "tsc: Type \'(null | undefined; badge: string; } | { value: string; label: string | { value: string; label: string; })[]\' is not assignable to type \'DropdownOption[]\'.\\n Type \'null | undefined; badge: string; } | { value: string; label: string | { value: string; label: string; }\' is not assignable to type \'DropdownOption\'.\\n Type \'{ value: string; label: null | string | undefined; badge: string; }\' is not assignable to type \'DropdownOption\'.\\n Types of property \'label\' are incompatible.\\n Type \'string | null | undefined\' is not assignable to type \'string\'.\\n Type \'undefined\' is not assignable to type \'string\'.", "179721187"] ], "src/shared/components/form/node-with-profile-form.tsx:1982864313": [ - [12, 8, 6, "tsc: Type \'\\"Attribute\\" | \\"Attribute\\" | \\"Component\\" | \\"Component\\" | \\"Group\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Parent\\" | \\"Profile\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { id?: string | undefined; choices?: { id?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; documentation?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash: string; } | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; icon?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\' is not assignable to type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'.\\n Type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\' is missing 2 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'", "2166186324"] + [12, 8, 6, "tsc: Type \'\\"Attribute\\" | \\"Attribute\\" | \\"Bandwidth\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Checkbox\\" | \\"Color\\" | \\"Color\\" | \\"Component\\" | \\"Component\\" | \\"DateTime\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Dropdown\\" | \\"Email\\" | \\"Email\\" | \\"File\\" | \\"File\\" | \\"Group\\" | \\"Group\\" | \\"HashedPassword\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"Hierarchy\\" | \\"ID\\" | \\"ID\\" | \\"IPHost\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"JSON\\" | \\"MacAddress\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Parent\\" | \\"Password\\" | \\"Password\\" | \\"Profile\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; documentation?: string | undefined; excluded_values?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; icon?: string | undefined; identifier?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\' is not assignable to type \'\\"Attribute\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Color\\" | \\"Component\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Email\\" | \\"File\\" | \\"Group\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"ID\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Password\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\'.\\n Type \'\\"Attribute\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Color\\" | \\"Component\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Email\\" | \\"File\\" | \\"Group\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"ID\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Password\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\' is missing 2 properties from type \'\\"Attribute\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Color\\" | \\"Component\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Email\\" | \\"File\\" | \\"Group\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"ID\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Password\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; children?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; generate_profile: boolean; generate_template: boolean; hierarchy?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; parent?: string | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\'", "2166186324"] ], "src/shared/components/form/utils/shouldAllowEmptySubmission.test.ts:2916106780": [ - [61, 19, 44, "tsc: Conversion of type \'{ attributes: never[]; }\' to type \'ModelSchema\' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to \'unknown\' first.\\n Type \'{ attributes: never[]; }\' is missing 5 properties from type \'\\"Attribute\\" | \\"Component\\" | \\"Group\\" | \\"Hierarchy\\" | \\"Parent\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"absent\\"; end_range: number; start_range: number; number_pool_id?: string | \\"absent\\"; min_value?: number | \\"absent\\"; name: string; description?: string | \\"absent\\"; name: string; kind: string; enum?: unknown[] | \\"absent\\"; name: string; namespace: string; description?: string | \\"absent\\"; name: string; peer: string; kind: \\"Generic\\" | \\"absent\\"; regex?: string | \\"absent\\"; regex?: string | \\"absent\\"; } | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; parameters?: { id?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; }[] | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; attributes?: { id?: string | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { id?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { [key: string]: unknown; } | undefined; deprecation?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; hash: string; } | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; kind?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; on_delete?: \\"no-action\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; allow_override: \\"none\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string\'", "1893999184"] + [61, 19, 44, "tsc: Conversion of type \'{ attributes: never[]; }\' to type \'ModelSchema\' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to \'unknown\' first.\\n Type \'{ attributes: never[]; }\' is missing 5 properties from type \'\\"Attribute\\" | \\"Bandwidth\\" | \\"Boolean\\" | \\"Checkbox\\" | \\"Color\\" | \\"Component\\" | \\"DateTime\\" | \\"Dropdown\\" | \\"Email\\" | \\"File\\" | \\"Group\\" | \\"HashedPassword\\" | \\"Hierarchy\\" | \\"ID\\" | \\"IPHost\\" | \\"IPNetwork\\" | \\"JSON\\" | \\"MacAddress\\" | \\"Parent\\" | \\"Password\\" | \\"Profile\\" | \\"Template\\"; label?: string | \\"TextArea\\"; enum?: unknown[] | \\"URL\\"; enum?: unknown[] | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; allow_override: \\"none\\" | \\"absent\\"; attributes?: ({ id?: string | \\"absent\\"; on_delete?: \\"no-action\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\" | \\"agnostic\\"; default_filter?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; deprecation?: string | \\"any\\"; read_only: boolean; deprecation?: string | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"aware\\" | \\"cascade\\" | \\"extra\\"; parameters?: Record | \\"extra\\"; parameters?: { end_range: number; start_range: number; number_pool_id?: string | \\"extra\\"; parameters?: { min_value?: number | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; parameters?: { regex?: string | \\"extra\\"; }[] | \\"inbound\\"; hierarchical?: string | \\"many\\"; min_count: number; max_count: number; common_parent?: string | \\"outbound\\" | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | null | undefined; allow_override: \\"none\\" | undefined; branch: \\"local\\" | undefined; cardinality: \\"one\\" | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; choices?: { name: string; description?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; color?: string | undefined; common_relatives?: string[] | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; computed_attribute?: { kind: \\"Jinja2\\"; jinja2_template: string; } | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; description?: string | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display: \\"default\\" | undefined; display_label?: string | undefined; display_labels?: string[] | undefined; documentation?: string | undefined; excluded_values?: string | undefined; hash?: string | undefined; human_friendly_id?: string[] | undefined; icon?: string | undefined; identifier?: string | undefined; include_in_menu?: boolean | undefined; inherit_from?: string[] | undefined; inherited: boolean; direction: \\"bidirectional\\" | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; label?: string | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_length?: number | undefined; max_value?: number | undefined; menu_placement?: string | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; min_length?: number | undefined; name: string; kind: \\"Any\\" | undefined; name: string; kind: \\"List\\"; enum?: unknown[] | undefined; name: string; kind: \\"Number\\"; enum?: unknown[] | undefined; name: string; kind: \\"NumberPool\\"; enum?: unknown[] | undefined; name: string; kind: \\"Text\\" | undefined; name: string; namespace: string; description?: string | undefined; name: string; peer: string; kind: \\"Generic\\" | undefined; optional: boolean; branch?: \\"local\\" | undefined; order_by?: string[] | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; order_weight?: number | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; ordered: boolean; default_value?: unknown; inherited: boolean; state: \\"present\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; read_only: boolean; unique: boolean; optional: boolean; branch?: \\"local\\" | undefined; readonly kind: string; } | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; regex?: string | undefined; relationships?: { id?: string | undefined; state: \\"present\\" | undefined; state: \\"present\\" | undefined; uniqueness_constraints?: string[][] | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; } | undefined; })[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | undefined; }[] | { id?: string | { id?: string | { id?: string | { id?: string | { id?: string | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"TransformPython\\"; transform: string; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; } | { kind: \\"User\\"; }\'", "1893999184"] ], "src/shared/components/form/utils/updateFormFieldValue.ts:2093845453": [ [45, 30, 8, "tsc: Argument of type \'null | { id: string; } | { id: string; }[]\' is not assignable to parameter of type \'NodeCore | NodeCore[] | boolean | null | number | string | string[]\'.\\n Type \'{ id: string; }\' is not assignable to type \'NodeCore | NodeCore[] | boolean | null | number | string | string[]\'.\\n Property \'__typename\' is missing in type \'{ id: string; }\' but required in type \'NodeCore\'.", "288015442"], diff --git a/frontend/app/src/entities/events/ui/filters/global-filter-form.tsx b/frontend/app/src/entities/events/ui/filters/global-filter-form.tsx index 15be8a21bf6..d82138c89ab 100644 --- a/frontend/app/src/entities/events/ui/filters/global-filter-form.tsx +++ b/frontend/app/src/entities/events/ui/filters/global-filter-form.tsx @@ -3,11 +3,11 @@ import { Form, FormField, FormSubmit } from "@/shared/components/ui/form"; import { useFilters } from "@/entities/nodes/filters/ui/hooks/use-filters"; import { DynamicFilterInput } from "@/entities/nodes/object/ui/filters/dynamic-filter-input"; -import type { AttributeSchema, RelationshipSchema } from "@/entities/schema/domain/model/schema"; +import type { FilterFieldSchema } from "@/entities/schema/domain/model/schema"; export type GlobalFilterFormProps = { name: string; - fieldSchema: AttributeSchema | RelationshipSchema; + fieldSchema: FilterFieldSchema; onSuccess?: () => void; }; diff --git a/frontend/app/src/entities/events/ui/filters/global-filter.tsx b/frontend/app/src/entities/events/ui/filters/global-filter.tsx index c22265d9256..caf5d77c96a 100644 --- a/frontend/app/src/entities/events/ui/filters/global-filter.tsx +++ b/frontend/app/src/entities/events/ui/filters/global-filter.tsx @@ -7,7 +7,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/shared/components/ui/ import { useFilters } from "@/entities/nodes/filters/ui/hooks/use-filters"; import { getNodeLabel } from "@/entities/nodes/object/domain/rules/get-node-label"; -import type { AttributeSchema, RelationshipSchema } from "@/entities/schema/domain/model/schema"; +import type { FilterFieldSchema } from "@/entities/schema/domain/model/schema"; import { GlobalFilterForm } from "./global-filter-form"; import { FilterTag } from "./global-filter-tag"; @@ -15,7 +15,7 @@ import { FilterTag } from "./global-filter-tag"; interface FilterTagProps extends TagProps { label: React.ReactNode; name: string; - fieldSchema: AttributeSchema | RelationshipSchema; + fieldSchema: FilterFieldSchema; } export function GlobalFilter({ label, name, fieldSchema, ...props }: FilterTagProps) { diff --git a/frontend/app/src/entities/events/ui/filters/global-kind-filter.tsx b/frontend/app/src/entities/events/ui/filters/global-kind-filter.tsx index 1b4ac49ebb5..7de11df4839 100644 --- a/frontend/app/src/entities/events/ui/filters/global-kind-filter.tsx +++ b/frontend/app/src/entities/events/ui/filters/global-kind-filter.tsx @@ -8,7 +8,6 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/shared/components/ui/ import { useFilters } from "@/entities/nodes/filters/ui/hooks/use-filters"; import type { NodeCore } from "@/entities/nodes/object/domain/model/node"; import { getNodeLabel } from "@/entities/nodes/object/domain/rules/get-node-label"; -import type { AttributeSchema, RelationshipSchema } from "@/entities/schema/domain/model/schema"; import { FilterTag } from "./global-filter-tag"; import { GlobalKindFilterForm } from "./global-kind-filter-form"; @@ -16,10 +15,9 @@ import { GlobalKindFilterForm } from "./global-kind-filter-form"; interface FilterTagProps extends TagProps { label: React.ReactNode; name: string; - fieldSchema: AttributeSchema | RelationshipSchema; } -export function GlobalKindFilter({ label, name, fieldSchema, ...props }: FilterTagProps) { +export function GlobalKindFilter({ label, name, ...props }: FilterTagProps) { const [filters, setFilters] = useFilters(); const [showFilters, setShowFilters] = useState(false); diff --git a/frontend/app/src/entities/nodes/object/ui/filters/dynamic-filter-input.tsx b/frontend/app/src/entities/nodes/object/ui/filters/dynamic-filter-input.tsx index 389ee6a6cdf..bb1d9519e5f 100644 --- a/frontend/app/src/entities/nodes/object/ui/filters/dynamic-filter-input.tsx +++ b/frontend/app/src/entities/nodes/object/ui/filters/dynamic-filter-input.tsx @@ -10,20 +10,16 @@ import { warnUnexpectedType } from "@/shared/utils/common"; import { RelationshipFilterCombobox } from "@/entities/nodes/object/ui/filters/relationship-filter-combobox"; import { ATTRIBUTE_KIND } from "@/entities/schema/domain/model/attribute-kind"; -import type { - AttributeKind, - AttributeSchema, - RelationshipSchema, -} from "@/entities/schema/domain/model/schema"; +import type { AttributeKind, FilterFieldSchema } from "@/entities/schema/domain/model/schema"; export interface DynamicFilterInputProps { - fieldSchema: AttributeSchema | RelationshipSchema; + fieldSchema: FilterFieldSchema; value: any; onChange: (value: any) => any; } export function DynamicFilterInput({ fieldSchema, value, onChange }: DynamicFilterInputProps) { - if ("peer" in fieldSchema) { + if (fieldSchema.peer) { return ; } diff --git a/frontend/app/src/entities/nodes/object/ui/object-table/object-table-schema-selector.tsx b/frontend/app/src/entities/nodes/object/ui/object-table/object-table-schema-selector.tsx index 10598be0091..aaa910a981c 100644 --- a/frontend/app/src/entities/nodes/object/ui/object-table/object-table-schema-selector.tsx +++ b/frontend/app/src/entities/nodes/object/ui/object-table/object-table-schema-selector.tsx @@ -60,7 +60,7 @@ export function ObjectTableSchemaSelector() { > { const pruned = removeFiltersNotInSchema(filters, baseSchema); @@ -78,7 +78,7 @@ export function ObjectTableSchemaSelector() { { const pruned = removeFiltersNotInSchema(filters, schema); diff --git a/frontend/app/src/entities/schema/domain/model/schema.ts b/frontend/app/src/entities/schema/domain/model/schema.ts index ed913dca9bc..296f42c15eb 100644 --- a/frontend/app/src/entities/schema/domain/model/schema.ts +++ b/frontend/app/src/entities/schema/domain/model/schema.ts @@ -9,14 +9,37 @@ export type TemplateSchema = components["schemas"]["APITemplateSchema"]; export type ModelSchema = GenericSchema | NodeSchema | ProfileSchema | TemplateSchema; -export type RelationshipSchema = components["schemas"]["RelationshipSchema"]; +export type RelationshipSchema = components["schemas"]["RelationshipSchemaRead"]; + +export type AttributeSchema = + | components["schemas"]["TextAttributeRead"] + | components["schemas"]["NumberAttributeRead"] + | components["schemas"]["ListAttributeRead"] + | components["schemas"]["NumberPoolAttributeRead"] + | components["schemas"]["GenericAttributeRead"]; + +/** + * Minimal field descriptor consumed by the filter UI. It is satisfied both by a real read + * `AttributeSchema`/`RelationshipSchema` and by the lightweight synthetic descriptors the global + * event filters build (e.g. `{ kind: "Dropdown", choices }` or `{ peer: "CoreAccount" }`). A truthy + * `peer` marks a relationship field; otherwise `kind` selects the attribute input. + */ +export type FilterFieldSchema = { + kind?: string; + peer?: string; + enum?: unknown[] | null; + choices?: components["schemas"]["DropdownChoiceRead"][] | null; +}; -export type AttributeSchema = components["schemas"]["AttributeSchema-Output"]; +export type ComputedAttribute = + | components["schemas"]["ComputedAttributeJinja2Read"] + | components["schemas"]["ComputedAttributeTransformPythonRead"] + | components["schemas"]["ComputedAttributeUserRead"]; export type AttributeKind = (typeof ATTRIBUTE_KIND)[keyof typeof ATTRIBUTE_KIND]; -export type TextAttributeParameters = components["schemas"]["TextAttributeParameters"]; -export type NumberAttributeParameters = components["schemas"]["NumberAttributeParameters"]; +export type TextAttributeParameters = components["schemas"]["TextAttributeParametersRead"]; +export type NumberAttributeParameters = components["schemas"]["NumberAttributeParametersRead"]; export type Namespace = { name: string; diff --git a/frontend/app/src/entities/schema/ui/attribute-display.tsx b/frontend/app/src/entities/schema/ui/attribute-display.tsx index b157f5e5ade..bfa6bca6956 100644 --- a/frontend/app/src/entities/schema/ui/attribute-display.tsx +++ b/frontend/app/src/entities/schema/ui/attribute-display.tsx @@ -7,6 +7,7 @@ import { Link } from "@/shared/components/ui/link"; import { formatNumberDisplay } from "@/shared/utils/number"; import { NodeLabel } from "@/entities/nodes/object/ui/node-label"; +import type { AttributeSchema } from "@/entities/schema/domain/model/schema"; import { ComputedAttributeDisplay } from "./computed-attribute-display"; import { AccordionStyled, NullDisplay, PropertyRow, PropertyTitle } from "./styled"; @@ -16,7 +17,7 @@ export const AttributeDisplay = ({ onKindClick, defaultOpen = false, }: { - attribute: components["schemas"]["AttributeSchema-Output"]; + attribute: AttributeSchema; onKindClick?: (kind: string) => void; defaultOpen?: boolean; }) => { @@ -80,7 +81,7 @@ export const AttributeDisplay = ({ const ChoicesRow = ({ choices, }: { - choices: components["schemas"]["DropdownChoice"][] | null | undefined; + choices: components["schemas"]["DropdownChoiceRead"][] | null | undefined; }) => { if (choices === undefined) return null; if (choices === null) return ; @@ -112,13 +113,9 @@ const ChoicesRow = ({ ); }; -const AttributeParameters = ({ - attribute, -}: { - attribute: components["schemas"]["AttributeSchema-Output"]; -}) => { +const AttributeParameters = ({ attribute }: { attribute: AttributeSchema }) => { if (attribute.kind === "Text") { - const parameters = attribute.parameters as components["schemas"]["TextAttributeParameters"]; + const parameters = attribute.parameters as components["schemas"]["TextAttributeParametersRead"]; return (
@@ -133,7 +130,8 @@ const AttributeParameters = ({ } if (attribute.kind === "Number") { - const parameters = attribute.parameters as components["schemas"]["NumberAttributeParameters"]; + const parameters = + attribute.parameters as components["schemas"]["NumberAttributeParametersRead"]; return (
@@ -148,7 +146,7 @@ const AttributeParameters = ({ } if (attribute.kind === "NumberPool") { - const parameters = attribute.parameters as components["schemas"]["NumberPoolParameters"]; + const parameters = attribute.parameters as components["schemas"]["NumberPoolParametersRead"]; return (
diff --git a/frontend/app/src/entities/schema/ui/computed-attribute-display.tsx b/frontend/app/src/entities/schema/ui/computed-attribute-display.tsx index 6bf04b27593..b443f068704 100644 --- a/frontend/app/src/entities/schema/ui/computed-attribute-display.tsx +++ b/frontend/app/src/entities/schema/ui/computed-attribute-display.tsx @@ -2,18 +2,19 @@ import { Button, Modal } from "@infrahub/ui"; import { EyeIcon } from "lucide-react"; import { DialogTrigger } from "react-aria-components"; -import type { components } from "@/shared/api/rest/types.generated"; import { Row } from "@/shared/components/container"; import { DataViewer } from "@/shared/components/data-viewer/data-viewer"; import { Badge } from "@/shared/components/ui/badge"; +import type { ComputedAttribute } from "@/entities/schema/domain/model/schema"; + import { SchemaKindDisplay } from "./styled"; export const ComputedAttributeDisplay = ({ computedAttribute, onKindClick, }: { - computedAttribute?: components["schemas"]["ComputedAttribute-Output"] | null; + computedAttribute?: ComputedAttribute | null; onKindClick?: (kind: string) => void; }) => { if (!computedAttribute) { diff --git a/frontend/app/src/shared/api/rest/types.generated.ts b/frontend/app/src/shared/api/rest/types.generated.ts index 988fffc59ac..f87a796cfc4 100644 --- a/frontend/app/src/shared/api/rest/types.generated.ts +++ b/frontend/app/src/shared/api/rest/types.generated.ts @@ -611,11 +611,6 @@ export interface components { * @description The ID of the node */ id?: string | null; - /** - * @description Expected state of the node/generic after loading the schema - * @default present - */ - state: components["schemas"]["HashableModelState"]; /** * Name * @description Node name, must be unique within a namespace and must start with an uppercase letter. @@ -691,16 +686,26 @@ export interface components { * @description Link to a documentation associated with this object, can be internal or external. */ documentation?: string | null; + /** + * @description Expected state of the node/generic after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; /** * Attributes * @description Node attributes */ - attributes?: components["schemas"]["AttributeSchema-Output"][]; + attributes?: (components["schemas"]["TextAttributeRead"] | components["schemas"]["NumberAttributeRead"] | components["schemas"]["ListAttributeRead"] | components["schemas"]["NumberPoolAttributeRead"] | components["schemas"]["GenericAttributeRead"])[]; /** * Relationships * @description Node Relationships */ - relationships?: components["schemas"]["RelationshipSchema"][]; + relationships?: components["schemas"]["RelationshipSchemaRead"][]; + /** + * Hash + * @description Hash of the node computed by the server. + */ + hash?: string | null; /** * Hierarchical * @description Defines if the Generic support the hierarchical mode. @@ -724,9 +729,7 @@ export interface components { */ restricted_namespaces?: string[] | null; /** Kind */ - kind?: string | null; - /** Hash */ - hash: string; + readonly kind: string; }; /** APINodeSchema */ APINodeSchema: { @@ -735,11 +738,6 @@ export interface components { * @description The ID of the node */ id?: string | null; - /** - * @description Expected state of the node/generic after loading the schema - * @default present - */ - state: components["schemas"]["HashableModelState"]; /** * Name * @description Node name, must be unique within a namespace and must start with an uppercase letter. @@ -815,16 +813,26 @@ export interface components { * @description Link to a documentation associated with this object, can be internal or external. */ documentation?: string | null; + /** + * @description Expected state of the node/generic after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; /** * Attributes * @description Node attributes */ - attributes?: components["schemas"]["AttributeSchema-Output"][]; + attributes?: (components["schemas"]["TextAttributeRead"] | components["schemas"]["NumberAttributeRead"] | components["schemas"]["ListAttributeRead"] | components["schemas"]["NumberPoolAttributeRead"] | components["schemas"]["GenericAttributeRead"])[]; /** * Relationships * @description Node Relationships */ - relationships?: components["schemas"]["RelationshipSchema"][]; + relationships?: components["schemas"]["RelationshipSchemaRead"][]; + /** + * Hash + * @description Hash of the node computed by the server. + */ + hash?: string | null; /** * Inherit From * @description List of Generic Kind that this node is inheriting from @@ -858,9 +866,7 @@ export interface components { */ children?: string | null; /** Kind */ - kind?: string | null; - /** Hash */ - hash: string; + readonly kind: string; }; /** APIProfileSchema */ APIProfileSchema: { @@ -869,11 +875,6 @@ export interface components { * @description The ID of the node */ id?: string | null; - /** - * @description Expected state of the node/generic after loading the schema - * @default present - */ - state: components["schemas"]["HashableModelState"]; /** * Name * @description Node name, must be unique within a namespace and must start with an uppercase letter. @@ -949,25 +950,33 @@ export interface components { * @description Link to a documentation associated with this object, can be internal or external. */ documentation?: string | null; + /** + * @description Expected state of the node/generic after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; /** * Attributes * @description Node attributes */ - attributes?: components["schemas"]["AttributeSchema-Output"][]; + attributes?: (components["schemas"]["TextAttributeRead"] | components["schemas"]["NumberAttributeRead"] | components["schemas"]["ListAttributeRead"] | components["schemas"]["NumberPoolAttributeRead"] | components["schemas"]["GenericAttributeRead"])[]; /** * Relationships * @description Node Relationships */ - relationships?: components["schemas"]["RelationshipSchema"][]; + relationships?: components["schemas"]["RelationshipSchemaRead"][]; + /** + * Hash + * @description Hash of the node computed by the server. + */ + hash?: string | null; /** * Inherit From * @description List of Generic Kind that this profile is inheriting from */ inherit_from?: string[]; /** Kind */ - kind?: string | null; - /** Hash */ - hash: string; + readonly kind: string; }; /** APITemplateSchema */ APITemplateSchema: { @@ -976,11 +985,6 @@ export interface components { * @description The ID of the node */ id?: string | null; - /** - * @description Expected state of the node/generic after loading the schema - * @default present - */ - state: components["schemas"]["HashableModelState"]; /** * Name * @description Node name, must be unique within a namespace and must start with an uppercase letter. @@ -1056,25 +1060,33 @@ export interface components { * @description Link to a documentation associated with this object, can be internal or external. */ documentation?: string | null; + /** + * @description Expected state of the node/generic after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; /** * Attributes * @description Node attributes */ - attributes?: components["schemas"]["AttributeSchema-Output"][]; + attributes?: (components["schemas"]["TextAttributeRead"] | components["schemas"]["NumberAttributeRead"] | components["schemas"]["ListAttributeRead"] | components["schemas"]["NumberPoolAttributeRead"] | components["schemas"]["GenericAttributeRead"])[]; /** * Relationships * @description Node Relationships */ - relationships?: components["schemas"]["RelationshipSchema"][]; + relationships?: components["schemas"]["RelationshipSchemaRead"][]; + /** + * Hash + * @description Hash of the node computed by the server. + */ + hash?: string | null; /** * Inherit From * @description List of Generic Kind that this template is inheriting from */ inherit_from?: string[]; /** Kind */ - kind?: string | null; - /** Hash */ - hash: string; + readonly kind: string; }; /** AccessTokenResponse */ AccessTokenResponse: { @@ -1115,47 +1127,252 @@ export interface components { /** Display Label */ display_label?: string | null; }; - /** AttributeParameters */ - AttributeParameters: { + /** AttributeParametersRead */ + AttributeParametersRead: Record; + /** AttributeParametersWrite */ + AttributeParametersWrite: Record; + /** Body_upload_file_api_storage_upload_file_post */ + Body_upload_file_api_storage_upload_file_post: { + /** File */ + file: string; + }; + /** BranchDiffArtifact */ + BranchDiffArtifact: { + /** Branch */ + branch: string; /** Id */ - id?: string | null; - /** @default present */ - state: components["schemas"]["HashableModelState"]; + id: string; + /** Display Label */ + display_label?: string | null; + action: components["schemas"]["DiffAction"]; + target?: components["schemas"]["ArtifactTarget"] | null; + item_new?: components["schemas"]["BranchDiffArtifactStorage"] | null; + item_previous?: components["schemas"]["BranchDiffArtifactStorage"] | null; + }; + /** BranchDiffArtifactStorage */ + BranchDiffArtifactStorage: { + /** Storage Id */ + storage_id: string; + /** Checksum */ + checksum: string; + }; + /** BranchDiffFile */ + BranchDiffFile: { + /** Branch */ + branch: string; + /** Location */ + location: string; + action: components["schemas"]["DiffAction"]; + }; + /** BranchDiffRepository */ + BranchDiffRepository: { + /** Branch */ + branch: string; + /** Id */ + id: string; + /** Display Name */ + display_name?: string | null; + /** Commit From */ + commit_from: string; + /** Commit To */ + commit_to: string; + /** Files */ + files?: components["schemas"]["BranchDiffFile"][]; + }; + /** + * BranchSupportType + * @enum {string} + */ + BranchSupportType: "aware" | "agnostic" | "local"; + /** ComputedAttributeJinja2Read */ + ComputedAttributeJinja2Read: { + /** + * @description Defines how the value of the attribute is computed. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "Jinja2"; + /** + * Jinja2 Template + * @description Jinja2 template used to compute the value, required when kind is Jinja2. + */ + jinja2_template: string; + }; + /** ComputedAttributeJinja2Write */ + ComputedAttributeJinja2Write: { + /** + * @description Defines how the value of the attribute is computed. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "Jinja2"; + /** + * Jinja2 Template + * @description Jinja2 template used to compute the value, required when kind is Jinja2. + */ + jinja2_template: string; + }; + /** ComputedAttributeTransformPythonRead */ + ComputedAttributeTransformPythonRead: { + /** + * @description Defines how the value of the attribute is computed. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "TransformPython"; + /** + * Transform + * @description Python transform name or ID, required when kind is TransformPython. + */ + transform: string; + }; + /** ComputedAttributeTransformPythonWrite */ + ComputedAttributeTransformPythonWrite: { + /** + * @description Defines how the value of the attribute is computed. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "TransformPython"; + /** + * Transform + * @description Python transform name or ID, required when kind is TransformPython. + */ + transform: string; + }; + /** ComputedAttributeUserRead */ + ComputedAttributeUserRead: { + /** + * @description Defines how the value of the attribute is computed. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "User"; + }; + /** ComputedAttributeUserWrite */ + ComputedAttributeUserWrite: { + /** + * @description Defines how the value of the attribute is computed. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "User"; + }; + /** ConfigAPI */ + ConfigAPI: { + main: components["schemas"]["MainSettings"]; + logging: components["schemas"]["LoggingSettings"]; + analytics: components["schemas"]["AnalyticsSettings"]; + experimental_features: components["schemas"]["ExperimentalFeaturesSettings"]; + sso: components["schemas"]["SSOInfo"]; + ldap: components["schemas"]["LDAPInfo"]; + /** Installation Type */ + installation_type: string; + policy: components["schemas"]["PolicySettings"]; + }; + /** + * DiffAction + * @enum {string} + */ + DiffAction: "added" | "removed" | "updated" | "unchanged"; + /** DropdownChoiceRead */ + DropdownChoiceRead: { + /** + * Name + * @description Name of the choice, must be unique within the dropdown. + */ + name: string; + /** + * Description + * @description Description of the choice. + */ + description?: string | null; + /** + * Color + * @description Color of the choice, must be a valid HTML color code. + */ + color?: string | null; + /** + * Label + * @description Human friendly representation of the choice. + */ + label?: string | null; + }; + /** DropdownChoiceWrite */ + DropdownChoiceWrite: { + /** + * Name + * @description Name of the choice, must be unique within the dropdown. + */ + name: string; + /** + * Description + * @description Description of the choice. + */ + description?: string | null; + /** + * Color + * @description Color of the choice, must be a valid HTML color code. + */ + color?: string | null; + /** + * Label + * @description Human friendly representation of the choice. + */ + label?: string | null; + }; + /** EnterpriseRequiredResponse */ + EnterpriseRequiredResponse: { + /** + * Error Code + * @default ENTERPRISE_REQUIRED + */ + error_code: string; + /** Feature */ + feature: string; + /** Message */ + message?: string | null; + }; + /** ExperimentalFeaturesSettings */ + ExperimentalFeaturesSettings: { + /** + * Graphql Enums + * @default false + */ + graphql_enums: boolean; + /** + * Value Db Index + * @deprecated + * @default false + */ + value_db_index: boolean; }; - /** AttributeSchema */ - "AttributeSchema-Input": { + /** GenericAttributeRead */ + GenericAttributeRead: { /** * Id * @description The ID of the attribute */ id?: string | null; - /** - * @description Expected state of the attribute after loading the schema - * @default present - */ - state: components["schemas"]["HashableModelState"]; /** * Name * @description Attribute name, must be unique within a model and must be all lowercase. */ name: string; /** - * Kind - * @description Defines the type of the attribute. + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} */ - kind: string; + kind: "Any" | "Bandwidth" | "Boolean" | "Checkbox" | "Color" | "DateTime" | "Dropdown" | "Email" | "File" | "HashedPassword" | "ID" | "IPHost" | "IPNetwork" | "JSON" | "MacAddress" | "Password" | "URL"; /** * Enum * @description Define a list of valid values for the attribute. */ enum?: unknown[] | null; - /** @description Defines how the value of this attribute will be populated. */ - computed_attribute?: components["schemas"]["ComputedAttribute-Input"] | null; + /** + * Computed Attribute + * @description Defines how the value of this attribute will be populated. + */ + computed_attribute?: (components["schemas"]["ComputedAttributeUserRead"] | components["schemas"]["ComputedAttributeJinja2Read"] | components["schemas"]["ComputedAttributeTransformPythonRead"]) | null; /** * Choices * @description Define a list of valid choices for a dropdown attribute. */ - choices?: components["schemas"]["DropdownChoice"][] | null; + choices?: components["schemas"]["DropdownChoiceRead"][] | null; /** * Regex * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) @@ -1223,16 +1440,16 @@ export interface components { * @default false */ inherited: boolean; + /** + * @description Expected state of the attribute after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; /** * @description Type of allowed override for the attribute. * @default any */ allow_override: components["schemas"]["AllowOverrideType"]; - /** - * Parameters - * @description Extra parameters specific to this kind of attribute - */ - parameters?: components["schemas"]["AttributeParameters"] | components["schemas"]["ListAttributeParameters"] | components["schemas"]["TextAttributeParameters"] | components["schemas"]["NumberAttributeParameters"] | components["schemas"]["NumberPoolParameters"]; /** * Deprecation * @description Mark attribute as deprecated and provide a user-friendly message to display @@ -1243,41 +1460,41 @@ export interface components { * @default default */ display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["AttributeParametersRead"] | null; }; - /** AttributeSchema */ - "AttributeSchema-Output": { + /** GenericAttributeWrite */ + GenericAttributeWrite: { /** * Id * @description The ID of the attribute */ id?: string | null; - /** - * @description Expected state of the attribute after loading the schema - * @default present - */ - state: components["schemas"]["HashableModelState"]; /** * Name * @description Attribute name, must be unique within a model and must be all lowercase. */ name: string; /** - * Kind - * @description Defines the type of the attribute. + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} */ - kind: string; + kind: "Any" | "Bandwidth" | "Boolean" | "Checkbox" | "Color" | "DateTime" | "Dropdown" | "Email" | "File" | "HashedPassword" | "ID" | "IPHost" | "IPNetwork" | "JSON" | "MacAddress" | "Password" | "URL"; /** * Enum * @description Define a list of valid values for the attribute. */ enum?: unknown[] | null; - /** @description Defines how the value of this attribute will be populated. */ - computed_attribute?: components["schemas"]["ComputedAttribute-Output"] | null; + /** + * Computed Attribute + * @description Defines how the value of this attribute will be populated. + */ + computed_attribute?: (components["schemas"]["ComputedAttributeUserWrite"] | components["schemas"]["ComputedAttributeJinja2Write"] | components["schemas"]["ComputedAttributeTransformPythonWrite"]) | null; /** * Choices * @description Define a list of valid choices for a dropdown attribute. */ - choices?: components["schemas"]["DropdownChoice"][] | null; + choices?: components["schemas"]["DropdownChoiceWrite"][] | null; /** * Regex * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) @@ -1340,21 +1557,15 @@ export interface components { */ default_value?: unknown | null; /** - * Inherited - * @description Internal value to indicate if the attribute was inherited from a Generic node. - * @default false + * @description Expected state of the attribute after loading the schema + * @default present */ - inherited: boolean; + state: components["schemas"]["SchemaState"]; /** * @description Type of allowed override for the attribute. * @default any */ allow_override: components["schemas"]["AllowOverrideType"]; - /** - * Parameters - * @description Extra parameters specific to this kind of attribute - */ - parameters?: components["schemas"]["AttributeParameters"] | components["schemas"]["ListAttributeParameters"] | components["schemas"]["TextAttributeParameters"] | components["schemas"]["NumberAttributeParameters"] | components["schemas"]["NumberPoolParameters"]; /** * Deprecation * @description Mark attribute as deprecated and provide a user-friendly message to display @@ -1365,159 +1576,16 @@ export interface components { * @default default */ display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["AttributeParametersWrite"] | null; }; - /** Body_upload_file_api_storage_upload_file_post */ - Body_upload_file_api_storage_upload_file_post: { - /** File */ - file: string; - }; - /** BranchDiffArtifact */ - BranchDiffArtifact: { - /** Branch */ - branch: string; - /** Id */ - id: string; - /** Display Label */ - display_label?: string | null; - action: components["schemas"]["DiffAction"]; - target?: components["schemas"]["ArtifactTarget"] | null; - item_new?: components["schemas"]["BranchDiffArtifactStorage"] | null; - item_previous?: components["schemas"]["BranchDiffArtifactStorage"] | null; - }; - /** BranchDiffArtifactStorage */ - BranchDiffArtifactStorage: { - /** Storage Id */ - storage_id: string; - /** Checksum */ - checksum: string; - }; - /** BranchDiffFile */ - BranchDiffFile: { - /** Branch */ - branch: string; - /** Location */ - location: string; - action: components["schemas"]["DiffAction"]; - }; - /** BranchDiffRepository */ - BranchDiffRepository: { - /** Branch */ - branch: string; - /** Id */ - id: string; - /** Display Name */ - display_name?: string | null; - /** Commit From */ - commit_from: string; - /** Commit To */ - commit_to: string; - /** Files */ - files?: components["schemas"]["BranchDiffFile"][]; - }; - /** - * BranchSupportType - * @enum {string} - */ - BranchSupportType: "aware" | "agnostic" | "local"; - /** ComputedAttribute */ - "ComputedAttribute-Input": { - /** Id */ - id?: string | null; - /** @default present */ - state: components["schemas"]["HashableModelState"]; - kind: components["schemas"]["ComputedAttributeKind"]; - /** - * Jinja2 Template - * @description The Jinja2 template in string format, required when assignment_type=jinja2 - */ - jinja2_template?: string | null; - /** - * Transform - * @description The Python Transform name or ID, required when assignment_type=transform - */ - transform?: string | null; - } & (unknown & unknown); - "ComputedAttribute-Output": { - [key: string]: unknown; - }; - /** - * ComputedAttributeKind - * @enum {string} - */ - ComputedAttributeKind: "User" | "Jinja2" | "TransformPython"; - /** ConfigAPI */ - ConfigAPI: { - main: components["schemas"]["MainSettings"]; - logging: components["schemas"]["LoggingSettings"]; - analytics: components["schemas"]["AnalyticsSettings"]; - experimental_features: components["schemas"]["ExperimentalFeaturesSettings"]; - sso: components["schemas"]["SSOInfo"]; - ldap: components["schemas"]["LDAPInfo"]; - /** Installation Type */ - installation_type: string; - policy: components["schemas"]["PolicySettings"]; - }; - /** - * DiffAction - * @enum {string} - */ - DiffAction: "added" | "removed" | "updated" | "unchanged"; - /** DropdownChoice */ - DropdownChoice: { - /** Id */ - id?: string | null; - /** @default present */ - state: components["schemas"]["HashableModelState"]; - /** Name */ - name: string; - /** Description */ - description?: string | null; - /** Color */ - color?: string | null; - /** Label */ - label?: string | null; - }; - /** EnterpriseRequiredResponse */ - EnterpriseRequiredResponse: { - /** - * Error Code - * @default ENTERPRISE_REQUIRED - */ - error_code: string; - /** Feature */ - feature: string; - /** Message */ - message?: string | null; - }; - /** ExperimentalFeaturesSettings */ - ExperimentalFeaturesSettings: { - /** - * Graphql Enums - * @default false - */ - graphql_enums: boolean; - /** - * Value Db Index - * @deprecated - * @default false - */ - value_db_index: boolean; - }; - /** - * GenericSchema - * @description A Generic can be either an Interface or a Union depending if there are some Attributes or Relationships defined. - */ - GenericSchema: { + /** GenericSchemaWrite */ + GenericSchemaWrite: { /** * Id * @description The ID of the node */ id?: string | null; - /** - * @description Expected state of the node/generic after loading the schema - * @default present - */ - state: components["schemas"]["HashableModelState"]; /** * Name * @description Node name, must be unique within a namespace and must start with an uppercase letter. @@ -1593,16 +1661,21 @@ export interface components { * @description Link to a documentation associated with this object, can be internal or external. */ documentation?: string | null; + /** + * @description Expected state of the node/generic after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; /** * Attributes * @description Node attributes */ - attributes?: components["schemas"]["AttributeSchema-Input"][]; + attributes?: (components["schemas"]["TextAttributeWrite"] | components["schemas"]["NumberAttributeWrite"] | components["schemas"]["ListAttributeWrite"] | components["schemas"]["NumberPoolAttributeWrite"] | components["schemas"]["GenericAttributeWrite"])[]; /** * Relationships * @description Node Relationships */ - relationships?: components["schemas"]["RelationshipSchema"][]; + relationships?: components["schemas"]["RelationshipSchemaWrite"][]; /** * Hierarchical * @description Defines if the Generic support the hierarchical mode. @@ -1615,11 +1688,6 @@ export interface components { * @default true */ generate_profile: boolean; - /** - * Used By - * @description List of Nodes that are referencing this Generic - */ - used_by?: string[]; /** * Restricted Namespaces * @description Nodes inheriting from this Generic schema must belong to one of the listed namespaces @@ -1646,11 +1714,6 @@ export interface components { [key: string]: components["schemas"]["HashableModelDiff"] | null; }; }; - /** - * HashableModelState - * @enum {string} - */ - HashableModelState: "present" | "absent"; /** InfoAPI */ InfoAPI: { /** Deployment Id */ @@ -1756,351 +1819,1096 @@ export interface components { */ icon: string; }; - /** - * ListAttributeParameters - * @description Parameters for List attributes supporting regex validation on list items. - */ - ListAttributeParameters: { - /** Id */ - id?: string | null; - /** @default present */ - state: components["schemas"]["HashableModelState"]; + /** ListAttributeParametersRead */ + ListAttributeParametersRead: { /** * Regex * @description Regular expression that each list item value must match if defined */ regex?: string | null; }; - /** LoggingSettings */ - LoggingSettings: { + /** ListAttributeParametersWrite */ + ListAttributeParametersWrite: { /** - * @default { - * "enable": false - * } + * Regex + * @description Regular expression that each list item value must match if defined */ - remote: components["schemas"]["RemoteLoggingSettings"]; + regex?: string | null; }; - /** MainSettings */ - MainSettings: { + /** ListAttributeRead */ + ListAttributeRead: { /** - * Docs Index Path - * Format: path - * @description Full path of saved json containing pre-indexed documentation - * @default /opt/infrahub/docs/build/search-index.json - */ - docs_index_path: string; - /** Internal Address */ - internal_address?: string | null; - /** - * Allow Anonymous Access - * @description Indicates if the system allows anonymous read access - * @default true + * Id + * @description The ID of the attribute */ - allow_anonymous_access: boolean; + id?: string | null; /** - * Anonymous Access Role - * @description Name of the role defining which permissions anonymous users have - * @default Anonymous User + * Name + * @description Attribute name, must be unique within a model and must be all lowercase. */ - anonymous_access_role: string; + name: string; /** - * Telemetry Optout - * @description Disable anonymous usage reporting - * @default false + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} */ - telemetry_optout: boolean; + kind: "List"; /** - * Telemetry Endpoint - * @default https://telemetry.opsmill.cloud/infrahub + * Enum + * @description Define a list of valid values for the attribute. */ - telemetry_endpoint: string; + enum?: unknown[] | null; /** - * Permission Backends - * @description List of modules to handle permissions, they will be run in the given order - * @default [ - * "infrahub.permissions.LocalPermissionBackend" - * ] + * Computed Attribute + * @description Defines how the value of this attribute will be populated. */ - permission_backends: string[]; + computed_attribute?: (components["schemas"]["ComputedAttributeUserRead"] | components["schemas"]["ComputedAttributeJinja2Read"] | components["schemas"]["ComputedAttributeTransformPythonRead"]) | null; /** - * Public Url - * @description Define the public URL of the Infrahub, might be required for OAuth2 and OIDC depending on your infrastructure. + * Choices + * @description Define a list of valid choices for a dropdown attribute. */ - public_url?: string | null; + choices?: components["schemas"]["DropdownChoiceRead"][] | null; /** - * Schema Strict Mode - * @description Enable strict schema validation. When set to `False`, `human_friendly_id` schema fields should not necessarily target a unique combination of peer attributes. - * @default true + * Regex + * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) */ - schema_strict_mode: boolean; + regex?: string | null; /** - * Diff Update After Merge - * @description When enabled, diff updates are triggered for active branches after a branch merge. - * @default true + * Max Length + * @description Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead) */ - diff_update_after_merge: boolean; + max_length?: number | null; /** - * Delete Branch After Merge - * @description When enabled, the Infrahub branch is automatically deleted after a successful merge. - * @default false + * Min Length + * @description Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead) */ - delete_branch_after_merge: boolean; + min_length?: number | null; /** - * Merge Failure Grace Period Seconds - * @description How long a branch may stay in MERGING with a dead merge-lock holder before it is flagged MERGE_FAILED. - * @default 180 + * Label + * @description Human friendly representation of the name. Will be autogenerated if not provided */ - merge_failure_grace_period_seconds: number; - }; - /** Menu */ - Menu: { - /** Sections */ - sections?: { - [key: string]: components["schemas"]["MenuItemList"][]; - }; - }; - /** MenuItemList */ - MenuItemList: { - /** Id */ - id?: string | null; + label?: string | null; /** - * Namespace - * @description Namespace of the menu item + * Description + * @description Short description of the attribute. */ - namespace: string; + description?: string | null; /** - * Name - * @description Name of the menu item + * Read Only + * @description Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object. + * @default false */ - name: string; + read_only: boolean; /** - * Description - * @description Description of the menu item - * @default + * Unique + * @description Indicate if the value of this attribute must be unique in the database for a given model. + * @default false */ - description: string; + unique: boolean; /** - * Protected - * @description Whether the menu item is protected + * Optional + * @description Indicate if this attribute is mandatory or optional. * @default false */ - protected: boolean; + optional: boolean; + /** @description Type of branch support for the attribute, if not defined it will be inherited from the node. */ + branch?: components["schemas"]["BranchSupportType"] | null; /** - * Label - * @description Title of the menu item + * Order Weight + * @description Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first. */ - label: string; + order_weight?: number | null; /** - * Path - * @description URL endpoint if applicable - * @default + * Ordered + * @description Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict. + * @default true */ - path: string; + ordered: boolean; /** - * Icon - * @description The icon to show for the current view + * Default Value + * @description Default value of the attribute. + */ + default_value?: unknown | null; + /** + * Inherited + * @description Internal value to indicate if the attribute was inherited from a Generic node. + * @default false + */ + inherited: boolean; + /** + * @description Expected state of the attribute after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; + /** + * @description Type of allowed override for the attribute. + * @default any + */ + allow_override: components["schemas"]["AllowOverrideType"]; + /** + * Deprecation + * @description Mark attribute as deprecated and provide a user-friendly message to display + */ + deprecation?: string | null; + /** + * @description Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section. + * @default default + */ + display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["ListAttributeParametersRead"] | null; + }; + /** ListAttributeWrite */ + ListAttributeWrite: { + /** + * Id + * @description The ID of the attribute + */ + id?: string | null; + /** + * Name + * @description Attribute name, must be unique within a model and must be all lowercase. + */ + name: string; + /** + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "List"; + /** + * Enum + * @description Define a list of valid values for the attribute. + */ + enum?: unknown[] | null; + /** + * Computed Attribute + * @description Defines how the value of this attribute will be populated. + */ + computed_attribute?: (components["schemas"]["ComputedAttributeUserWrite"] | components["schemas"]["ComputedAttributeJinja2Write"] | components["schemas"]["ComputedAttributeTransformPythonWrite"]) | null; + /** + * Choices + * @description Define a list of valid choices for a dropdown attribute. + */ + choices?: components["schemas"]["DropdownChoiceWrite"][] | null; + /** + * Regex + * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) + */ + regex?: string | null; + /** + * Max Length + * @description Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead) + */ + max_length?: number | null; + /** + * Min Length + * @description Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead) + */ + min_length?: number | null; + /** + * Label + * @description Human friendly representation of the name. Will be autogenerated if not provided + */ + label?: string | null; + /** + * Description + * @description Short description of the attribute. + */ + description?: string | null; + /** + * Read Only + * @description Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object. + * @default false + */ + read_only: boolean; + /** + * Unique + * @description Indicate if the value of this attribute must be unique in the database for a given model. + * @default false + */ + unique: boolean; + /** + * Optional + * @description Indicate if this attribute is mandatory or optional. + * @default false + */ + optional: boolean; + /** @description Type of branch support for the attribute, if not defined it will be inherited from the node. */ + branch?: components["schemas"]["BranchSupportType"] | null; + /** + * Order Weight + * @description Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first. + */ + order_weight?: number | null; + /** + * Ordered + * @description Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict. + * @default true + */ + ordered: boolean; + /** + * Default Value + * @description Default value of the attribute. + */ + default_value?: unknown | null; + /** + * @description Expected state of the attribute after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; + /** + * @description Type of allowed override for the attribute. + * @default any + */ + allow_override: components["schemas"]["AllowOverrideType"]; + /** + * Deprecation + * @description Mark attribute as deprecated and provide a user-friendly message to display + */ + deprecation?: string | null; + /** + * @description Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section. + * @default default + */ + display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["ListAttributeParametersWrite"] | null; + }; + /** LoggingSettings */ + LoggingSettings: { + /** + * @default { + * "enable": false + * } + */ + remote: components["schemas"]["RemoteLoggingSettings"]; + }; + /** MainSettings */ + MainSettings: { + /** + * Docs Index Path + * Format: path + * @description Full path of saved json containing pre-indexed documentation + * @default /opt/infrahub/docs/build/search-index.json + */ + docs_index_path: string; + /** Internal Address */ + internal_address?: string | null; + /** + * Allow Anonymous Access + * @description Indicates if the system allows anonymous read access + * @default true + */ + allow_anonymous_access: boolean; + /** + * Anonymous Access Role + * @description Name of the role defining which permissions anonymous users have + * @default Anonymous User + */ + anonymous_access_role: string; + /** + * Telemetry Optout + * @description Disable anonymous usage reporting + * @default false + */ + telemetry_optout: boolean; + /** + * Telemetry Endpoint + * @default https://telemetry.opsmill.cloud/infrahub + */ + telemetry_endpoint: string; + /** + * Permission Backends + * @description List of modules to handle permissions, they will be run in the given order + * @default [ + * "infrahub.permissions.LocalPermissionBackend" + * ] + */ + permission_backends: string[]; + /** + * Public Url + * @description Define the public URL of the Infrahub, might be required for OAuth2 and OIDC depending on your infrastructure. + */ + public_url?: string | null; + /** + * Schema Strict Mode + * @description Enable strict schema validation. When set to `False`, `human_friendly_id` schema fields should not necessarily target a unique combination of peer attributes. + * @default true + */ + schema_strict_mode: boolean; + /** + * Diff Update After Merge + * @description When enabled, diff updates are triggered for active branches after a branch merge. + * @default true + */ + diff_update_after_merge: boolean; + /** + * Delete Branch After Merge + * @description When enabled, the Infrahub branch is automatically deleted after a successful merge. + * @default false + */ + delete_branch_after_merge: boolean; + /** + * Merge Failure Grace Period Seconds + * @description How long a branch may stay in MERGING with a dead merge-lock holder before it is flagged MERGE_FAILED. + * @default 180 + */ + merge_failure_grace_period_seconds: number; + }; + /** Menu */ + Menu: { + /** Sections */ + sections?: { + [key: string]: components["schemas"]["MenuItemList"][]; + }; + }; + /** MenuItemList */ + MenuItemList: { + /** Id */ + id?: string | null; + /** + * Namespace + * @description Namespace of the menu item + */ + namespace: string; + /** + * Name + * @description Name of the menu item + */ + name: string; + /** + * Description + * @description Description of the menu item * @default */ - icon: string; + description: string; + /** + * Protected + * @description Whether the menu item is protected + * @default false + */ + protected: boolean; + /** + * Label + * @description Title of the menu item + */ + label: string; + /** + * Path + * @description URL endpoint if applicable + * @default + */ + path: string; + /** + * Icon + * @description The icon to show for the current view + * @default + */ + icon: string; + /** + * Kind + * @description Kind of the model associated with this menuitem if applicable + * @default + */ + kind: string; + /** + * Order Weight + * @default 5000 + */ + order_weight: number; + /** @default object */ + section: components["schemas"]["MenuSection"]; + /** Permissions */ + permissions?: string[]; + /** + * Children + * @description Child objects + */ + children?: components["schemas"]["MenuItemList"][]; + /** Identifier */ + readonly identifier: string; + }; + /** + * MenuSection + * @enum {string} + */ + MenuSection: "object" | "internal"; + /** NodeExtensionWrite */ + NodeExtensionWrite: { + /** + * Kind + * @description Kind of the existing node to extend. + */ + kind: string; + /** + * Attributes + * @description Attributes to add to the existing node. + */ + attributes?: (components["schemas"]["TextAttributeWrite"] | components["schemas"]["NumberAttributeWrite"] | components["schemas"]["ListAttributeWrite"] | components["schemas"]["NumberPoolAttributeWrite"] | components["schemas"]["GenericAttributeWrite"])[]; + /** + * Relationships + * @description Relationships to add to the existing node. + */ + relationships?: components["schemas"]["RelationshipSchemaWrite"][]; + }; + /** NodeSchemaWrite */ + NodeSchemaWrite: { + /** + * Id + * @description The ID of the node + */ + id?: string | null; + /** + * Name + * @description Node name, must be unique within a namespace and must start with an uppercase letter. + */ + name: string; + /** + * Namespace + * @description Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions. + */ + namespace: string; + /** + * Description + * @description Short description of the model, will be visible in the frontend. + */ + description?: string | null; + /** + * Label + * @description Human friendly representation of the name/kind + */ + label?: string | null; + /** + * @description Type of branch support for the model. + * @default aware + */ + branch: components["schemas"]["BranchSupportType"]; + /** + * Default Filter + * @description Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead) + */ + default_filter?: string | null; + /** + * Human Friendly Id + * @description Human friendly and unique identifier for the object. + */ + human_friendly_id?: string[] | null; + /** + * Display Label + * @description Attribute or Jinja2 template to use to generate the display label + */ + display_label?: string | null; + /** + * Display Labels + * @description List of attributes to use to generate the display label (deprecated) + */ + display_labels?: string[] | null; + /** + * Include In Menu + * @description Defines if objects of this kind should be included in the menu. + */ + include_in_menu?: boolean | null; + /** + * Menu Placement + * @description Defines where in the menu this object should be placed. + */ + menu_placement?: string | null; + /** + * Icon + * @description Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/ + */ + icon?: string | null; + /** + * Order By + * @description List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc. + */ + order_by?: string[] | null; + /** + * Uniqueness Constraints + * @description List of multi-element uniqueness constraints that can combine relationships and attributes + */ + uniqueness_constraints?: string[][] | null; + /** + * Documentation + * @description Link to a documentation associated with this object, can be internal or external. + */ + documentation?: string | null; + /** + * @description Expected state of the node/generic after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; + /** + * Attributes + * @description Node attributes + */ + attributes?: (components["schemas"]["TextAttributeWrite"] | components["schemas"]["NumberAttributeWrite"] | components["schemas"]["ListAttributeWrite"] | components["schemas"]["NumberPoolAttributeWrite"] | components["schemas"]["GenericAttributeWrite"])[]; + /** + * Relationships + * @description Node Relationships + */ + relationships?: components["schemas"]["RelationshipSchemaWrite"][]; + /** + * Inherit From + * @description List of Generic Kind that this node is inheriting from + */ + inherit_from?: string[]; + /** + * Generate Profile + * @description Indicate if a profile schema should be generated for this schema + * @default true + */ + generate_profile: boolean; + /** + * Generate Template + * @description Indicate if an object template schema should be generated for this schema + * @default false + */ + generate_template: boolean; + /** + * Parent + * @description Expected Kind for the parent node in a Hierarchy, default to the main generic defined if not defined. + */ + parent?: string | null; + /** + * Children + * @description Expected Kind for the children nodes in a Hierarchy, default to the main generic defined if not defined. + */ + children?: string | null; + }; + /** NumberAttributeParametersRead */ + NumberAttributeParametersRead: { + /** + * Min Value + * @description Set a minimum value allowed. + */ + min_value?: number | null; + /** + * Max Value + * @description Set a maximum value allowed. + */ + max_value?: number | null; + /** + * Excluded Values + * @description List of values or range of values not allowed for the attribute, format is: '100,150-200,280,300-400' + */ + excluded_values?: string | null; + }; + /** NumberAttributeParametersWrite */ + NumberAttributeParametersWrite: { + /** + * Min Value + * @description Set a minimum value allowed. + */ + min_value?: number | null; + /** + * Max Value + * @description Set a maximum value allowed. + */ + max_value?: number | null; + /** + * Excluded Values + * @description List of values or range of values not allowed for the attribute, format is: '100,150-200,280,300-400' + */ + excluded_values?: string | null; + }; + /** NumberAttributeRead */ + NumberAttributeRead: { + /** + * Id + * @description The ID of the attribute + */ + id?: string | null; + /** + * Name + * @description Attribute name, must be unique within a model and must be all lowercase. + */ + name: string; + /** + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "Number"; + /** + * Enum + * @description Define a list of valid values for the attribute. + */ + enum?: unknown[] | null; + /** + * Computed Attribute + * @description Defines how the value of this attribute will be populated. + */ + computed_attribute?: (components["schemas"]["ComputedAttributeUserRead"] | components["schemas"]["ComputedAttributeJinja2Read"] | components["schemas"]["ComputedAttributeTransformPythonRead"]) | null; + /** + * Choices + * @description Define a list of valid choices for a dropdown attribute. + */ + choices?: components["schemas"]["DropdownChoiceRead"][] | null; + /** + * Regex + * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) + */ + regex?: string | null; + /** + * Max Length + * @description Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead) + */ + max_length?: number | null; + /** + * Min Length + * @description Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead) + */ + min_length?: number | null; + /** + * Label + * @description Human friendly representation of the name. Will be autogenerated if not provided + */ + label?: string | null; + /** + * Description + * @description Short description of the attribute. + */ + description?: string | null; + /** + * Read Only + * @description Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object. + * @default false + */ + read_only: boolean; + /** + * Unique + * @description Indicate if the value of this attribute must be unique in the database for a given model. + * @default false + */ + unique: boolean; + /** + * Optional + * @description Indicate if this attribute is mandatory or optional. + * @default false + */ + optional: boolean; + /** @description Type of branch support for the attribute, if not defined it will be inherited from the node. */ + branch?: components["schemas"]["BranchSupportType"] | null; + /** + * Order Weight + * @description Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first. + */ + order_weight?: number | null; + /** + * Ordered + * @description Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict. + * @default true + */ + ordered: boolean; + /** + * Default Value + * @description Default value of the attribute. + */ + default_value?: unknown | null; /** - * Kind - * @description Kind of the model associated with this menuitem if applicable - * @default + * Inherited + * @description Internal value to indicate if the attribute was inherited from a Generic node. + * @default false */ - kind: string; + inherited: boolean; /** - * Order Weight - * @default 5000 + * @description Expected state of the attribute after loading the schema + * @default present */ - order_weight: number; - /** @default object */ - section: components["schemas"]["MenuSection"]; - /** Permissions */ - permissions?: string[]; + state: components["schemas"]["SchemaState"]; /** - * Children - * @description Child objects + * @description Type of allowed override for the attribute. + * @default any */ - children?: components["schemas"]["MenuItemList"][]; - /** Identifier */ - readonly identifier: string; - }; - /** - * MenuSection - * @enum {string} - */ - MenuSection: "object" | "internal"; - /** NodeExtensionSchema */ - NodeExtensionSchema: { - /** Id */ - id?: string | null; - /** @default present */ - state: components["schemas"]["HashableModelState"]; - /** Kind */ - kind: string; - /** Attributes */ - attributes?: components["schemas"]["AttributeSchema-Input"][]; - /** Relationships */ - relationships?: components["schemas"]["RelationshipSchema"][]; + allow_override: components["schemas"]["AllowOverrideType"]; + /** + * Deprecation + * @description Mark attribute as deprecated and provide a user-friendly message to display + */ + deprecation?: string | null; + /** + * @description Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section. + * @default default + */ + display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["NumberAttributeParametersRead"] | null; }; - /** NodeSchema */ - NodeSchema: { + /** NumberAttributeWrite */ + NumberAttributeWrite: { /** * Id - * @description The ID of the node + * @description The ID of the attribute */ id?: string | null; /** - * @description Expected state of the node/generic after loading the schema + * Name + * @description Attribute name, must be unique within a model and must be all lowercase. + */ + name: string; + /** + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "Number"; + /** + * Enum + * @description Define a list of valid values for the attribute. + */ + enum?: unknown[] | null; + /** + * Computed Attribute + * @description Defines how the value of this attribute will be populated. + */ + computed_attribute?: (components["schemas"]["ComputedAttributeUserWrite"] | components["schemas"]["ComputedAttributeJinja2Write"] | components["schemas"]["ComputedAttributeTransformPythonWrite"]) | null; + /** + * Choices + * @description Define a list of valid choices for a dropdown attribute. + */ + choices?: components["schemas"]["DropdownChoiceWrite"][] | null; + /** + * Regex + * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) + */ + regex?: string | null; + /** + * Max Length + * @description Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead) + */ + max_length?: number | null; + /** + * Min Length + * @description Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead) + */ + min_length?: number | null; + /** + * Label + * @description Human friendly representation of the name. Will be autogenerated if not provided + */ + label?: string | null; + /** + * Description + * @description Short description of the attribute. + */ + description?: string | null; + /** + * Read Only + * @description Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object. + * @default false + */ + read_only: boolean; + /** + * Unique + * @description Indicate if the value of this attribute must be unique in the database for a given model. + * @default false + */ + unique: boolean; + /** + * Optional + * @description Indicate if this attribute is mandatory or optional. + * @default false + */ + optional: boolean; + /** @description Type of branch support for the attribute, if not defined it will be inherited from the node. */ + branch?: components["schemas"]["BranchSupportType"] | null; + /** + * Order Weight + * @description Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first. + */ + order_weight?: number | null; + /** + * Ordered + * @description Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict. + * @default true + */ + ordered: boolean; + /** + * Default Value + * @description Default value of the attribute. + */ + default_value?: unknown | null; + /** + * @description Expected state of the attribute after loading the schema * @default present */ - state: components["schemas"]["HashableModelState"]; + state: components["schemas"]["SchemaState"]; + /** + * @description Type of allowed override for the attribute. + * @default any + */ + allow_override: components["schemas"]["AllowOverrideType"]; + /** + * Deprecation + * @description Mark attribute as deprecated and provide a user-friendly message to display + */ + deprecation?: string | null; + /** + * @description Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section. + * @default default + */ + display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["NumberAttributeParametersWrite"] | null; + }; + /** NumberPoolAttributeRead */ + NumberPoolAttributeRead: { + /** + * Id + * @description The ID of the attribute + */ + id?: string | null; /** * Name - * @description Node name, must be unique within a namespace and must start with an uppercase letter. + * @description Attribute name, must be unique within a model and must be all lowercase. */ name: string; /** - * Namespace - * @description Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions. + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} */ - namespace: string; + kind: "NumberPool"; + /** + * Enum + * @description Define a list of valid values for the attribute. + */ + enum?: unknown[] | null; + /** + * Computed Attribute + * @description Defines how the value of this attribute will be populated. + */ + computed_attribute?: (components["schemas"]["ComputedAttributeUserRead"] | components["schemas"]["ComputedAttributeJinja2Read"] | components["schemas"]["ComputedAttributeTransformPythonRead"]) | null; + /** + * Choices + * @description Define a list of valid choices for a dropdown attribute. + */ + choices?: components["schemas"]["DropdownChoiceRead"][] | null; + /** + * Regex + * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) + */ + regex?: string | null; + /** + * Max Length + * @description Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead) + */ + max_length?: number | null; + /** + * Min Length + * @description Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead) + */ + min_length?: number | null; + /** + * Label + * @description Human friendly representation of the name. Will be autogenerated if not provided + */ + label?: string | null; /** * Description - * @description Short description of the model, will be visible in the frontend. + * @description Short description of the attribute. */ description?: string | null; /** - * Label - * @description Human friendly representation of the name/kind + * Read Only + * @description Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object. + * @default false + */ + read_only: boolean; + /** + * Unique + * @description Indicate if the value of this attribute must be unique in the database for a given model. + * @default false + */ + unique: boolean; + /** + * Optional + * @description Indicate if this attribute is mandatory or optional. + * @default false + */ + optional: boolean; + /** @description Type of branch support for the attribute, if not defined it will be inherited from the node. */ + branch?: components["schemas"]["BranchSupportType"] | null; + /** + * Order Weight + * @description Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first. + */ + order_weight?: number | null; + /** + * Ordered + * @description Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict. + * @default true + */ + ordered: boolean; + /** + * Default Value + * @description Default value of the attribute. + */ + default_value?: unknown | null; + /** + * Inherited + * @description Internal value to indicate if the attribute was inherited from a Generic node. + * @default false + */ + inherited: boolean; + /** + * @description Expected state of the attribute after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; + /** + * @description Type of allowed override for the attribute. + * @default any + */ + allow_override: components["schemas"]["AllowOverrideType"]; + /** + * Deprecation + * @description Mark attribute as deprecated and provide a user-friendly message to display + */ + deprecation?: string | null; + /** + * @description Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section. + * @default default + */ + display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["NumberPoolParametersRead"] | null; + }; + /** NumberPoolAttributeWrite */ + NumberPoolAttributeWrite: { + /** + * Id + * @description The ID of the attribute */ - label?: string | null; + id?: string | null; /** - * @description Type of branch support for the model. - * @default aware + * Name + * @description Attribute name, must be unique within a model and must be all lowercase. */ - branch: components["schemas"]["BranchSupportType"]; + name: string; /** - * Default Filter - * @description Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead) + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} */ - default_filter?: string | null; + kind: "NumberPool"; /** - * Human Friendly Id - * @description Human friendly and unique identifier for the object. + * Enum + * @description Define a list of valid values for the attribute. */ - human_friendly_id?: string[] | null; + enum?: unknown[] | null; /** - * Display Label - * @description Attribute or Jinja2 template to use to generate the display label + * Computed Attribute + * @description Defines how the value of this attribute will be populated. */ - display_label?: string | null; + computed_attribute?: (components["schemas"]["ComputedAttributeUserWrite"] | components["schemas"]["ComputedAttributeJinja2Write"] | components["schemas"]["ComputedAttributeTransformPythonWrite"]) | null; /** - * Display Labels - * @description List of attributes to use to generate the display label (deprecated) + * Choices + * @description Define a list of valid choices for a dropdown attribute. */ - display_labels?: string[] | null; + choices?: components["schemas"]["DropdownChoiceWrite"][] | null; /** - * Include In Menu - * @description Defines if objects of this kind should be included in the menu. + * Regex + * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) */ - include_in_menu?: boolean | null; + regex?: string | null; /** - * Menu Placement - * @description Defines where in the menu this object should be placed. + * Max Length + * @description Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead) */ - menu_placement?: string | null; + max_length?: number | null; /** - * Icon - * @description Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/ + * Min Length + * @description Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead) */ - icon?: string | null; + min_length?: number | null; /** - * Order By - * @description List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc. + * Label + * @description Human friendly representation of the name. Will be autogenerated if not provided */ - order_by?: string[] | null; + label?: string | null; /** - * Uniqueness Constraints - * @description List of multi-element uniqueness constraints that can combine relationships and attributes + * Description + * @description Short description of the attribute. */ - uniqueness_constraints?: string[][] | null; + description?: string | null; /** - * Documentation - * @description Link to a documentation associated with this object, can be internal or external. + * Read Only + * @description Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object. + * @default false */ - documentation?: string | null; + read_only: boolean; /** - * Attributes - * @description Node attributes + * Unique + * @description Indicate if the value of this attribute must be unique in the database for a given model. + * @default false */ - attributes?: components["schemas"]["AttributeSchema-Input"][]; + unique: boolean; /** - * Relationships - * @description Node Relationships + * Optional + * @description Indicate if this attribute is mandatory or optional. + * @default false */ - relationships?: components["schemas"]["RelationshipSchema"][]; + optional: boolean; + /** @description Type of branch support for the attribute, if not defined it will be inherited from the node. */ + branch?: components["schemas"]["BranchSupportType"] | null; /** - * Inherit From - * @description List of Generic Kind that this node is inheriting from + * Order Weight + * @description Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first. */ - inherit_from?: string[]; + order_weight?: number | null; /** - * Generate Profile - * @description Indicate if a profile schema should be generated for this schema + * Ordered + * @description Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict. * @default true */ - generate_profile: boolean; + ordered: boolean; /** - * Generate Template - * @description Indicate if an object template schema should be generated for this schema - * @default false + * Default Value + * @description Default value of the attribute. */ - generate_template: boolean; + default_value?: unknown | null; /** - * Hierarchy - * @description Internal value to track the name of the Hierarchy, must match the name of a Generic supporting hierarchical mode + * @description Expected state of the attribute after loading the schema + * @default present */ - hierarchy?: string | null; + state: components["schemas"]["SchemaState"]; /** - * Parent - * @description Expected Kind for the parent node in a Hierarchy, default to the main generic defined if not defined. + * @description Type of allowed override for the attribute. + * @default any */ - parent?: string | null; + allow_override: components["schemas"]["AllowOverrideType"]; /** - * Children - * @description Expected Kind for the children nodes in a Hierarchy, default to the main generic defined if not defined. + * Deprecation + * @description Mark attribute as deprecated and provide a user-friendly message to display */ - children?: string | null; + deprecation?: string | null; + /** + * @description Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section. + * @default default + */ + display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["NumberPoolParametersWrite"] | null; }; - /** NumberAttributeParameters */ - NumberAttributeParameters: { - /** Id */ - id?: string | null; - /** @default present */ - state: components["schemas"]["HashableModelState"]; + /** NumberPoolParametersRead */ + NumberPoolParametersRead: { /** - * Min Value - * @description Set a minimum value allowed. + * End Range + * @description End range for numbers for the associated NumberPool + * @default 9223372036854776000 */ - min_value?: number | null; + end_range: number; /** - * Max Value - * @description Set a maximum value allowed. + * Start Range + * @description Start range for numbers for the associated NumberPool + * @default 1 */ - max_value?: number | null; + start_range: number; /** - * Excluded Values - * @description List of values or range of values not allowed for the attribute, format is: '100,150-200,280,300-400' + * Number Pool Id + * @description The ID of the numberpool associated with this attribute. Only set after the number pool has been provisioned. */ - excluded_values?: string | null; + number_pool_id?: string | null; }; - /** NumberPoolParameters */ - NumberPoolParameters: { - /** Id */ - id?: string | null; - /** @default present */ - state: components["schemas"]["HashableModelState"]; + /** NumberPoolParametersWrite */ + NumberPoolParametersWrite: { /** * End Range * @description End range for numbers for the associated NumberPool @@ -2174,18 +2982,13 @@ export interface components { * @enum {string} */ RelationshipKind: "Generic" | "Attribute" | "Component" | "Parent" | "Group" | "Hierarchy" | "Profile" | "Template"; - /** RelationshipSchema */ - RelationshipSchema: { + /** RelationshipSchemaRead */ + RelationshipSchemaRead: { /** * Id * @description The ID of the relationship schema */ id?: string | null; - /** - * @description Expected state of the relationship after loading the schema - * @default present - */ - state: components["schemas"]["HashableModelState"]; /** * Name * @description Relationship name, must be unique within a model and must be all lowercase. @@ -2261,17 +3064,133 @@ export interface components { * @description Internal value to indicate if the relationship was inherited from a Generic node. * @default false */ - inherited: boolean; + inherited: boolean; + /** + * @description Defines the direction of the relationship, Unidirectional relationship are required when the same model is on both side. + * @default bidirectional + */ + direction: components["schemas"]["RelationshipDirection"]; + /** + * Hierarchical + * @description Internal attribute to track the type of hierarchy this relationship is part of, must match a valid Generic Kind + */ + hierarchical?: string | null; + /** + * @description Expected state of the relationship after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; + /** @description Default is no-action. If cascade, related node(s) are deleted when this node is deleted. */ + on_delete?: components["schemas"]["RelationshipDeleteBehavior"] | null; + /** + * @description Type of allowed override for the relationship. + * @default any + */ + allow_override: components["schemas"]["AllowOverrideType"]; + /** + * Read Only + * @description Set the relationship as read-only, users won't be able to change its value. + * @default false + */ + read_only: boolean; + /** + * Deprecation + * @description Mark relationship as deprecated and provide a user-friendly message to display + */ + deprecation?: string | null; + /** + * @description Controls where the relationship is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section. + * @default default + */ + display: components["schemas"]["SchemaAttributeDisplay"]; + }; + /** RelationshipSchemaWrite */ + RelationshipSchemaWrite: { + /** + * Id + * @description The ID of the relationship schema + */ + id?: string | null; + /** + * Name + * @description Relationship name, must be unique within a model and must be all lowercase. + */ + name: string; + /** + * Peer + * @description Type (kind) of objects supported on the other end of the relationship. + */ + peer: string; + /** + * @description Defines the type of the relationship. + * @default Generic + */ + kind: components["schemas"]["RelationshipKind"]; + /** + * Label + * @description Human friendly representation of the name. Will be autogenerated if not provided + */ + label?: string | null; + /** + * Description + * @description Short description of the relationship. + */ + description?: string | null; + /** + * Identifier + * @description Unique identifier of the relationship within a model, identifiers must match to traverse a relationship on both direction. + */ + identifier?: string | null; + /** + * @description Defines how many objects are expected on the other side of the relationship. + * @default many + */ + cardinality: components["schemas"]["RelationshipCardinality"]; + /** + * Min Count + * @description Defines the minimum objects allowed on the other side of the relationship. + * @default 0 + */ + min_count: number; + /** + * Max Count + * @description Defines the maximum objects allowed on the other side of the relationship. + * @default 0 + */ + max_count: number; + /** + * Common Parent + * @description Name of a parent relationship on the peer schema that must share the same related object with the object's parent. + */ + common_parent?: string | null; + /** + * Common Relatives + * @description List of relationship names on the peer schema for which all objects must share the same set of peers. + */ + common_relatives?: string[] | null; + /** + * Order Weight + * @description Number used to order the relationship in the frontend (table and view). Lowest value will be ordered first. + */ + order_weight?: number | null; + /** + * Optional + * @description Indicate if this relationship is mandatory or optional. + * @default true + */ + optional: boolean; + /** @description Type of branch support for the relationship. If not defined, it will be determined based on both peers. */ + branch?: components["schemas"]["BranchSupportType"] | null; /** * @description Defines the direction of the relationship, Unidirectional relationship are required when the same model is on both side. * @default bidirectional */ direction: components["schemas"]["RelationshipDirection"]; /** - * Hierarchical - * @description Internal attribute to track the type of hierarchy this relationship is part of, must match a valid Generic Kind + * @description Expected state of the relationship after loading the schema + * @default present */ - hierarchical?: string | null; + state: components["schemas"]["SchemaState"]; /** @description Default is no-action. If cascade, related node(s) are deleted when this node is deleted. */ on_delete?: components["schemas"]["RelationshipDeleteBehavior"] | null; /** @@ -2374,30 +3293,23 @@ export interface components { [key: string]: components["schemas"]["HashableModelDiff"]; }; }; - /** SchemaExtension */ - SchemaExtension: { - /** Id */ - id?: string | null; - /** @default present */ - state: components["schemas"]["HashableModelState"]; - /** Nodes */ - nodes?: components["schemas"]["NodeExtensionSchema"][]; + /** SchemaExtensionWrite */ + SchemaExtensionWrite: { + /** + * Nodes + * @description Nodes to extend with additional attributes and relationships. + */ + nodes?: components["schemas"]["NodeExtensionWrite"][]; }; /** SchemaLoadAPI */ SchemaLoadAPI: { /** Version */ version: string; - /** Generics */ - generics?: components["schemas"]["GenericSchema"][]; /** Nodes */ - nodes?: components["schemas"]["NodeSchema"][]; - /** - * @default { - * "state": "present", - * "nodes": [] - * } - */ - extensions: components["schemas"]["SchemaExtension"]; + nodes?: components["schemas"]["NodeSchemaWrite"][]; + /** Generics */ + generics?: components["schemas"]["GenericSchemaWrite"][]; + extensions?: components["schemas"]["SchemaExtensionWrite"] | null; }; /** SchemaNamespace */ SchemaNamespace: { @@ -2424,6 +3336,11 @@ export interface components { /** Namespaces */ namespaces?: components["schemas"]["SchemaNamespace"][]; }; + /** + * SchemaState + * @enum {string} + */ + SchemaState: "present" | "absent"; /** SchemaUpdate */ SchemaUpdate: { /** @@ -2516,12 +3433,26 @@ export interface components { checksum: string; remote_send_status: components["schemas"]["RemoteSendStatus"]; }; - /** TextAttributeParameters */ - TextAttributeParameters: { - /** Id */ - id?: string | null; - /** @default present */ - state: components["schemas"]["HashableModelState"]; + /** TextAttributeParametersRead */ + TextAttributeParametersRead: { + /** + * Regex + * @description Regular expression that attribute value must match if defined + */ + regex?: string | null; + /** + * Min Length + * @description Set a minimum number of characters allowed. + */ + min_length?: number | null; + /** + * Max Length + * @description Set a maximum number of characters allowed. + */ + max_length?: number | null; + }; + /** TextAttributeParametersWrite */ + TextAttributeParametersWrite: { /** * Regex * @description Regular expression that attribute value must match if defined @@ -2538,6 +3469,244 @@ export interface components { */ max_length?: number | null; }; + /** TextAttributeRead */ + TextAttributeRead: { + /** + * Id + * @description The ID of the attribute + */ + id?: string | null; + /** + * Name + * @description Attribute name, must be unique within a model and must be all lowercase. + */ + name: string; + /** + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "Text" | "TextArea"; + /** + * Enum + * @description Define a list of valid values for the attribute. + */ + enum?: unknown[] | null; + /** + * Computed Attribute + * @description Defines how the value of this attribute will be populated. + */ + computed_attribute?: (components["schemas"]["ComputedAttributeUserRead"] | components["schemas"]["ComputedAttributeJinja2Read"] | components["schemas"]["ComputedAttributeTransformPythonRead"]) | null; + /** + * Choices + * @description Define a list of valid choices for a dropdown attribute. + */ + choices?: components["schemas"]["DropdownChoiceRead"][] | null; + /** + * Regex + * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) + */ + regex?: string | null; + /** + * Max Length + * @description Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead) + */ + max_length?: number | null; + /** + * Min Length + * @description Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead) + */ + min_length?: number | null; + /** + * Label + * @description Human friendly representation of the name. Will be autogenerated if not provided + */ + label?: string | null; + /** + * Description + * @description Short description of the attribute. + */ + description?: string | null; + /** + * Read Only + * @description Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object. + * @default false + */ + read_only: boolean; + /** + * Unique + * @description Indicate if the value of this attribute must be unique in the database for a given model. + * @default false + */ + unique: boolean; + /** + * Optional + * @description Indicate if this attribute is mandatory or optional. + * @default false + */ + optional: boolean; + /** @description Type of branch support for the attribute, if not defined it will be inherited from the node. */ + branch?: components["schemas"]["BranchSupportType"] | null; + /** + * Order Weight + * @description Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first. + */ + order_weight?: number | null; + /** + * Ordered + * @description Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict. + * @default true + */ + ordered: boolean; + /** + * Default Value + * @description Default value of the attribute. + */ + default_value?: unknown | null; + /** + * Inherited + * @description Internal value to indicate if the attribute was inherited from a Generic node. + * @default false + */ + inherited: boolean; + /** + * @description Expected state of the attribute after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; + /** + * @description Type of allowed override for the attribute. + * @default any + */ + allow_override: components["schemas"]["AllowOverrideType"]; + /** + * Deprecation + * @description Mark attribute as deprecated and provide a user-friendly message to display + */ + deprecation?: string | null; + /** + * @description Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section. + * @default default + */ + display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["TextAttributeParametersRead"] | null; + }; + /** TextAttributeWrite */ + TextAttributeWrite: { + /** + * Id + * @description The ID of the attribute + */ + id?: string | null; + /** + * Name + * @description Attribute name, must be unique within a model and must be all lowercase. + */ + name: string; + /** + * @description Defines the type of the attribute. (enum property replaced by openapi-typescript) + * @enum {string} + */ + kind: "Text" | "TextArea"; + /** + * Enum + * @description Define a list of valid values for the attribute. + */ + enum?: unknown[] | null; + /** + * Computed Attribute + * @description Defines how the value of this attribute will be populated. + */ + computed_attribute?: (components["schemas"]["ComputedAttributeUserWrite"] | components["schemas"]["ComputedAttributeJinja2Write"] | components["schemas"]["ComputedAttributeTransformPythonWrite"]) | null; + /** + * Choices + * @description Define a list of valid choices for a dropdown attribute. + */ + choices?: components["schemas"]["DropdownChoiceWrite"][] | null; + /** + * Regex + * @description Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead) + */ + regex?: string | null; + /** + * Max Length + * @description Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead) + */ + max_length?: number | null; + /** + * Min Length + * @description Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead) + */ + min_length?: number | null; + /** + * Label + * @description Human friendly representation of the name. Will be autogenerated if not provided + */ + label?: string | null; + /** + * Description + * @description Short description of the attribute. + */ + description?: string | null; + /** + * Read Only + * @description Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object. + * @default false + */ + read_only: boolean; + /** + * Unique + * @description Indicate if the value of this attribute must be unique in the database for a given model. + * @default false + */ + unique: boolean; + /** + * Optional + * @description Indicate if this attribute is mandatory or optional. + * @default false + */ + optional: boolean; + /** @description Type of branch support for the attribute, if not defined it will be inherited from the node. */ + branch?: components["schemas"]["BranchSupportType"] | null; + /** + * Order Weight + * @description Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first. + */ + order_weight?: number | null; + /** + * Ordered + * @description Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict. + * @default true + */ + ordered: boolean; + /** + * Default Value + * @description Default value of the attribute. + */ + default_value?: unknown | null; + /** + * @description Expected state of the attribute after loading the schema + * @default present + */ + state: components["schemas"]["SchemaState"]; + /** + * @description Type of allowed override for the attribute. + * @default any + */ + allow_override: components["schemas"]["AllowOverrideType"]; + /** + * Deprecation + * @description Mark attribute as deprecated and provide a user-friendly message to display + */ + deprecation?: string | null; + /** + * @description Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section. + * @default default + */ + display: components["schemas"]["SchemaAttributeDisplay"]; + /** @description Extra parameters specific to this kind of attribute */ + parameters?: components["schemas"]["TextAttributeParametersWrite"] | null; + }; /** UploadContentPayload */ UploadContentPayload: { /** Content */ diff --git a/frontend/app/src/shared/components/form/utils/getFormFieldFromAttribute.ts b/frontend/app/src/shared/components/form/utils/getFormFieldFromAttribute.ts index bc9adfb95a8..96da29e2910 100644 --- a/frontend/app/src/shared/components/form/utils/getFormFieldFromAttribute.ts +++ b/frontend/app/src/shared/components/form/utils/getFormFieldFromAttribute.ts @@ -131,7 +131,7 @@ export const getFormFieldFromAttribute = ({ type: ATTRIBUTE_KIND.DROPDOWN, schema, items: (attributeSchema.choices ?? []).map( - (choice: components["schemas"]["DropdownChoice"]) => ({ + (choice: components["schemas"]["DropdownChoiceRead"]) => ({ value: choice.name, label: choice.label ?? choice.name, color: choice.color ?? undefined, diff --git a/python_sdk b/python_sdk index 25cf9394da5..d15ce11be0d 160000 --- a/python_sdk +++ b/python_sdk @@ -1 +1 @@ -Subproject commit 25cf9394da583b43ffd124f2bcbcd748a4ec86b7 +Subproject commit d15ce11be0d0466d169f5fbfe3fccb49b283095e diff --git a/schema/openapi.json b/schema/openapi.json index 455b0cd44fd..1a04fc9ae8b 100644 --- a/schema/openapi.json +++ b/schema/openapi.json @@ -2439,14 +2439,7 @@ } ], "title": "Id", - "description": "The ID of the node", - "update": "not_applicable" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "description": "Expected state of the node/generic after loading the schema", - "default": "present", - "update": "not_applicable" + "description": "The ID of the node" }, "name": { "type": "string", @@ -2454,8 +2447,7 @@ "minLength": 2, "pattern": "^[A-Z][a-zA-Z0-9]+$", "title": "Name", - "description": "Node name, must be unique within a namespace and must start with an uppercase letter.", - "update": "migration_required" + "description": "Node name, must be unique within a namespace and must start with an uppercase letter." }, "namespace": { "type": "string", @@ -2463,8 +2455,7 @@ "minLength": 3, "pattern": "^[A-Z][a-z0-9]+$", "title": "Namespace", - "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions.", - "update": "migration_required" + "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions." }, "description": { "anyOf": [ @@ -2477,8 +2468,7 @@ } ], "title": "Description", - "description": "Short description of the model, will be visible in the frontend.", - "update": "allowed" + "description": "Short description of the model, will be visible in the frontend." }, "label": { "anyOf": [ @@ -2491,14 +2481,12 @@ } ], "title": "Label", - "description": "Human friendly representation of the name/kind", - "update": "allowed" + "description": "Human friendly representation of the name/kind" }, "branch": { "$ref": "#/components/schemas/BranchSupportType", "description": "Type of branch support for the model.", - "default": "aware", - "update": "not_supported" + "default": "aware" }, "default_filter": { "anyOf": [ @@ -2511,8 +2499,7 @@ } ], "title": "Default Filter", - "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)", - "update": "allowed" + "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)" }, "human_friendly_id": { "anyOf": [ @@ -2527,8 +2514,7 @@ } ], "title": "Human Friendly Id", - "description": "Human friendly and unique identifier for the object.", - "update": "allowed" + "description": "Human friendly and unique identifier for the object." }, "display_label": { "anyOf": [ @@ -2540,15 +2526,13 @@ } ], "title": "Display Label", - "description": "Attribute or Jinja2 template to use to generate the display label", - "update": "allowed" + "description": "Attribute or Jinja2 template to use to generate the display label" }, "display_labels": { "anyOf": [ { "items": { - "type": "string", - "deprecationMessage": "display_labels are deprecated use display_label instead" + "type": "string" }, "type": "array" }, @@ -2557,8 +2541,7 @@ } ], "title": "Display Labels", - "description": "List of attributes to use to generate the display label (deprecated)", - "update": "allowed" + "description": "List of attributes to use to generate the display label (deprecated)" }, "include_in_menu": { "anyOf": [ @@ -2570,8 +2553,7 @@ } ], "title": "Include In Menu", - "description": "Defines if objects of this kind should be included in the menu.", - "update": "allowed" + "description": "Defines if objects of this kind should be included in the menu." }, "menu_placement": { "anyOf": [ @@ -2583,8 +2565,7 @@ } ], "title": "Menu Placement", - "description": "Defines where in the menu this object should be placed.", - "update": "allowed" + "description": "Defines where in the menu this object should be placed." }, "icon": { "anyOf": [ @@ -2596,8 +2577,7 @@ } ], "title": "Icon", - "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/", - "update": "allowed" + "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/" }, "order_by": { "anyOf": [ @@ -2612,8 +2592,7 @@ } ], "title": "Order By", - "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc.", - "update": "allowed" + "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc." }, "uniqueness_constraints": { "anyOf": [ @@ -2631,8 +2610,7 @@ } ], "title": "Uniqueness Constraints", - "description": "List of multi-element uniqueness constraints that can combine relationships and attributes", - "update": "migration_required" + "description": "List of multi-element uniqueness constraints that can combine relationships and attributes" }, "documentation": { "anyOf": [ @@ -2644,40 +2622,95 @@ } ], "title": "Documentation", - "description": "Link to a documentation associated with this object, can be internal or external.", - "update": "allowed" + "description": "Link to a documentation associated with this object, can be internal or external." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the node/generic after loading the schema", + "default": "present" }, "attributes": { "items": { - "$ref": "#/components/schemas/AttributeSchema-Output" + "oneOf": [ + { + "$ref": "#/components/schemas/TextAttributeRead" + }, + { + "$ref": "#/components/schemas/NumberAttributeRead" + }, + { + "$ref": "#/components/schemas/ListAttributeRead" + }, + { + "$ref": "#/components/schemas/NumberPoolAttributeRead" + }, + { + "$ref": "#/components/schemas/GenericAttributeRead" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Any": "#/components/schemas/GenericAttributeRead", + "Bandwidth": "#/components/schemas/GenericAttributeRead", + "Boolean": "#/components/schemas/GenericAttributeRead", + "Checkbox": "#/components/schemas/GenericAttributeRead", + "Color": "#/components/schemas/GenericAttributeRead", + "DateTime": "#/components/schemas/GenericAttributeRead", + "Dropdown": "#/components/schemas/GenericAttributeRead", + "Email": "#/components/schemas/GenericAttributeRead", + "File": "#/components/schemas/GenericAttributeRead", + "HashedPassword": "#/components/schemas/GenericAttributeRead", + "ID": "#/components/schemas/GenericAttributeRead", + "IPHost": "#/components/schemas/GenericAttributeRead", + "IPNetwork": "#/components/schemas/GenericAttributeRead", + "JSON": "#/components/schemas/GenericAttributeRead", + "List": "#/components/schemas/ListAttributeRead", + "MacAddress": "#/components/schemas/GenericAttributeRead", + "Number": "#/components/schemas/NumberAttributeRead", + "NumberPool": "#/components/schemas/NumberPoolAttributeRead", + "Password": "#/components/schemas/GenericAttributeRead", + "Text": "#/components/schemas/TextAttributeRead", + "TextArea": "#/components/schemas/TextAttributeRead", + "URL": "#/components/schemas/GenericAttributeRead" + } + } }, "type": "array", "title": "Attributes", - "description": "Node attributes", - "update": "not_applicable" + "description": "Node attributes" }, "relationships": { "items": { - "$ref": "#/components/schemas/RelationshipSchema" + "$ref": "#/components/schemas/RelationshipSchemaRead" }, "type": "array", "title": "Relationships", - "description": "Node Relationships", - "update": "not_applicable" + "description": "Node Relationships" + }, + "hash": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Hash", + "description": "Hash of the node computed by the server." }, "hierarchical": { "type": "boolean", "title": "Hierarchical", "description": "Defines if the Generic support the hierarchical mode.", - "default": false, - "update": "validate_constraint" + "default": false }, "generate_profile": { "type": "boolean", "title": "Generate Profile", "description": "Indicate if a profile schema should be generated for this schema", - "default": true, - "update": "validate_constraint" + "default": true }, "used_by": { "items": { @@ -2685,8 +2718,7 @@ }, "type": "array", "title": "Used By", - "description": "List of Nodes that are referencing this Generic", - "update": "not_applicable" + "description": "List of Nodes that are referencing this Generic" }, "restricted_namespaces": { "anyOf": [ @@ -2701,31 +2733,19 @@ } ], "title": "Restricted Namespaces", - "description": "Nodes inheriting from this Generic schema must belong to one of the listed namespaces", - "update": "allowed" + "description": "Nodes inheriting from this Generic schema must belong to one of the listed namespaces" }, "kind": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Kind" - }, - "hash": { "type": "string", - "title": "Hash" + "title": "Kind", + "readOnly": true } }, - "additionalProperties": false, "type": "object", "required": [ "name", "namespace", - "hash" + "kind" ], "title": "APIGenericSchema" }, @@ -2741,14 +2761,7 @@ } ], "title": "Id", - "description": "The ID of the node", - "update": "not_applicable" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "description": "Expected state of the node/generic after loading the schema", - "default": "present", - "update": "not_applicable" + "description": "The ID of the node" }, "name": { "type": "string", @@ -2756,8 +2769,7 @@ "minLength": 2, "pattern": "^[A-Z][a-zA-Z0-9]+$", "title": "Name", - "description": "Node name, must be unique within a namespace and must start with an uppercase letter.", - "update": "migration_required" + "description": "Node name, must be unique within a namespace and must start with an uppercase letter." }, "namespace": { "type": "string", @@ -2765,8 +2777,7 @@ "minLength": 3, "pattern": "^[A-Z][a-z0-9]+$", "title": "Namespace", - "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions.", - "update": "migration_required" + "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions." }, "description": { "anyOf": [ @@ -2779,8 +2790,7 @@ } ], "title": "Description", - "description": "Short description of the model, will be visible in the frontend.", - "update": "allowed" + "description": "Short description of the model, will be visible in the frontend." }, "label": { "anyOf": [ @@ -2793,14 +2803,12 @@ } ], "title": "Label", - "description": "Human friendly representation of the name/kind", - "update": "allowed" + "description": "Human friendly representation of the name/kind" }, "branch": { "$ref": "#/components/schemas/BranchSupportType", "description": "Type of branch support for the model.", - "default": "aware", - "update": "not_supported" + "default": "aware" }, "default_filter": { "anyOf": [ @@ -2813,8 +2821,7 @@ } ], "title": "Default Filter", - "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)", - "update": "allowed" + "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)" }, "human_friendly_id": { "anyOf": [ @@ -2829,8 +2836,7 @@ } ], "title": "Human Friendly Id", - "description": "Human friendly and unique identifier for the object.", - "update": "allowed" + "description": "Human friendly and unique identifier for the object." }, "display_label": { "anyOf": [ @@ -2842,15 +2848,13 @@ } ], "title": "Display Label", - "description": "Attribute or Jinja2 template to use to generate the display label", - "update": "allowed" + "description": "Attribute or Jinja2 template to use to generate the display label" }, "display_labels": { "anyOf": [ { "items": { - "type": "string", - "deprecationMessage": "display_labels are deprecated use display_label instead" + "type": "string" }, "type": "array" }, @@ -2859,8 +2863,7 @@ } ], "title": "Display Labels", - "description": "List of attributes to use to generate the display label (deprecated)", - "update": "allowed" + "description": "List of attributes to use to generate the display label (deprecated)" }, "include_in_menu": { "anyOf": [ @@ -2872,8 +2875,7 @@ } ], "title": "Include In Menu", - "description": "Defines if objects of this kind should be included in the menu.", - "update": "allowed" + "description": "Defines if objects of this kind should be included in the menu." }, "menu_placement": { "anyOf": [ @@ -2885,8 +2887,7 @@ } ], "title": "Menu Placement", - "description": "Defines where in the menu this object should be placed.", - "update": "allowed" + "description": "Defines where in the menu this object should be placed." }, "icon": { "anyOf": [ @@ -2898,8 +2899,7 @@ } ], "title": "Icon", - "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/", - "update": "allowed" + "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/" }, "order_by": { "anyOf": [ @@ -2914,8 +2914,7 @@ } ], "title": "Order By", - "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc.", - "update": "allowed" + "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc." }, "uniqueness_constraints": { "anyOf": [ @@ -2933,8 +2932,7 @@ } ], "title": "Uniqueness Constraints", - "description": "List of multi-element uniqueness constraints that can combine relationships and attributes", - "update": "migration_required" + "description": "List of multi-element uniqueness constraints that can combine relationships and attributes" }, "documentation": { "anyOf": [ @@ -2946,26 +2944,83 @@ } ], "title": "Documentation", - "description": "Link to a documentation associated with this object, can be internal or external.", - "update": "allowed" + "description": "Link to a documentation associated with this object, can be internal or external." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the node/generic after loading the schema", + "default": "present" }, "attributes": { "items": { - "$ref": "#/components/schemas/AttributeSchema-Output" + "oneOf": [ + { + "$ref": "#/components/schemas/TextAttributeRead" + }, + { + "$ref": "#/components/schemas/NumberAttributeRead" + }, + { + "$ref": "#/components/schemas/ListAttributeRead" + }, + { + "$ref": "#/components/schemas/NumberPoolAttributeRead" + }, + { + "$ref": "#/components/schemas/GenericAttributeRead" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Any": "#/components/schemas/GenericAttributeRead", + "Bandwidth": "#/components/schemas/GenericAttributeRead", + "Boolean": "#/components/schemas/GenericAttributeRead", + "Checkbox": "#/components/schemas/GenericAttributeRead", + "Color": "#/components/schemas/GenericAttributeRead", + "DateTime": "#/components/schemas/GenericAttributeRead", + "Dropdown": "#/components/schemas/GenericAttributeRead", + "Email": "#/components/schemas/GenericAttributeRead", + "File": "#/components/schemas/GenericAttributeRead", + "HashedPassword": "#/components/schemas/GenericAttributeRead", + "ID": "#/components/schemas/GenericAttributeRead", + "IPHost": "#/components/schemas/GenericAttributeRead", + "IPNetwork": "#/components/schemas/GenericAttributeRead", + "JSON": "#/components/schemas/GenericAttributeRead", + "List": "#/components/schemas/ListAttributeRead", + "MacAddress": "#/components/schemas/GenericAttributeRead", + "Number": "#/components/schemas/NumberAttributeRead", + "NumberPool": "#/components/schemas/NumberPoolAttributeRead", + "Password": "#/components/schemas/GenericAttributeRead", + "Text": "#/components/schemas/TextAttributeRead", + "TextArea": "#/components/schemas/TextAttributeRead", + "URL": "#/components/schemas/GenericAttributeRead" + } + } }, "type": "array", "title": "Attributes", - "description": "Node attributes", - "update": "not_applicable" + "description": "Node attributes" }, "relationships": { "items": { - "$ref": "#/components/schemas/RelationshipSchema" + "$ref": "#/components/schemas/RelationshipSchemaRead" }, "type": "array", "title": "Relationships", - "description": "Node Relationships", - "update": "not_applicable" + "description": "Node Relationships" + }, + "hash": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Hash", + "description": "Hash of the node computed by the server." }, "inherit_from": { "items": { @@ -2973,22 +3028,19 @@ }, "type": "array", "title": "Inherit From", - "description": "List of Generic Kind that this node is inheriting from", - "update": "validate_constraint" + "description": "List of Generic Kind that this node is inheriting from" }, "generate_profile": { "type": "boolean", "title": "Generate Profile", "description": "Indicate if a profile schema should be generated for this schema", - "default": true, - "update": "validate_constraint" + "default": true }, "generate_template": { "type": "boolean", "title": "Generate Template", "description": "Indicate if an object template schema should be generated for this schema", - "default": false, - "update": "allowed" + "default": false }, "hierarchy": { "anyOf": [ @@ -3000,8 +3052,7 @@ } ], "title": "Hierarchy", - "description": "Internal value to track the name of the Hierarchy, must match the name of a Generic supporting hierarchical mode", - "update": "validate_constraint" + "description": "Internal value to track the name of the Hierarchy, must match the name of a Generic supporting hierarchical mode" }, "parent": { "anyOf": [ @@ -3013,8 +3064,7 @@ } ], "title": "Parent", - "description": "Expected Kind for the parent node in a Hierarchy, default to the main generic defined if not defined.", - "update": "validate_constraint" + "description": "Expected Kind for the parent node in a Hierarchy, default to the main generic defined if not defined." }, "children": { "anyOf": [ @@ -3026,31 +3076,19 @@ } ], "title": "Children", - "description": "Expected Kind for the children nodes in a Hierarchy, default to the main generic defined if not defined.", - "update": "validate_constraint" + "description": "Expected Kind for the children nodes in a Hierarchy, default to the main generic defined if not defined." }, "kind": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Kind" - }, - "hash": { "type": "string", - "title": "Hash" + "title": "Kind", + "readOnly": true } }, - "additionalProperties": false, "type": "object", "required": [ "name", "namespace", - "hash" + "kind" ], "title": "APINodeSchema" }, @@ -3066,14 +3104,7 @@ } ], "title": "Id", - "description": "The ID of the node", - "update": "not_applicable" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "description": "Expected state of the node/generic after loading the schema", - "default": "present", - "update": "not_applicable" + "description": "The ID of the node" }, "name": { "type": "string", @@ -3081,8 +3112,7 @@ "minLength": 2, "pattern": "^[A-Z][a-zA-Z0-9]+$", "title": "Name", - "description": "Node name, must be unique within a namespace and must start with an uppercase letter.", - "update": "migration_required" + "description": "Node name, must be unique within a namespace and must start with an uppercase letter." }, "namespace": { "type": "string", @@ -3090,8 +3120,7 @@ "minLength": 3, "pattern": "^[A-Z][a-z0-9]+$", "title": "Namespace", - "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions.", - "update": "migration_required" + "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions." }, "description": { "anyOf": [ @@ -3104,8 +3133,7 @@ } ], "title": "Description", - "description": "Short description of the model, will be visible in the frontend.", - "update": "allowed" + "description": "Short description of the model, will be visible in the frontend." }, "label": { "anyOf": [ @@ -3118,14 +3146,12 @@ } ], "title": "Label", - "description": "Human friendly representation of the name/kind", - "update": "allowed" + "description": "Human friendly representation of the name/kind" }, "branch": { "$ref": "#/components/schemas/BranchSupportType", "description": "Type of branch support for the model.", - "default": "aware", - "update": "not_supported" + "default": "aware" }, "default_filter": { "anyOf": [ @@ -3138,8 +3164,7 @@ } ], "title": "Default Filter", - "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)", - "update": "allowed" + "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)" }, "human_friendly_id": { "anyOf": [ @@ -3154,8 +3179,7 @@ } ], "title": "Human Friendly Id", - "description": "Human friendly and unique identifier for the object.", - "update": "allowed" + "description": "Human friendly and unique identifier for the object." }, "display_label": { "anyOf": [ @@ -3167,15 +3191,13 @@ } ], "title": "Display Label", - "description": "Attribute or Jinja2 template to use to generate the display label", - "update": "allowed" + "description": "Attribute or Jinja2 template to use to generate the display label" }, "display_labels": { "anyOf": [ { "items": { - "type": "string", - "deprecationMessage": "display_labels are deprecated use display_label instead" + "type": "string" }, "type": "array" }, @@ -3184,8 +3206,7 @@ } ], "title": "Display Labels", - "description": "List of attributes to use to generate the display label (deprecated)", - "update": "allowed" + "description": "List of attributes to use to generate the display label (deprecated)" }, "include_in_menu": { "anyOf": [ @@ -3197,8 +3218,7 @@ } ], "title": "Include In Menu", - "description": "Defines if objects of this kind should be included in the menu.", - "update": "allowed" + "description": "Defines if objects of this kind should be included in the menu." }, "menu_placement": { "anyOf": [ @@ -3210,8 +3230,7 @@ } ], "title": "Menu Placement", - "description": "Defines where in the menu this object should be placed.", - "update": "allowed" + "description": "Defines where in the menu this object should be placed." }, "icon": { "anyOf": [ @@ -3223,8 +3242,7 @@ } ], "title": "Icon", - "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/", - "update": "allowed" + "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/" }, "order_by": { "anyOf": [ @@ -3239,8 +3257,7 @@ } ], "title": "Order By", - "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc.", - "update": "allowed" + "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc." }, "uniqueness_constraints": { "anyOf": [ @@ -3258,8 +3275,7 @@ } ], "title": "Uniqueness Constraints", - "description": "List of multi-element uniqueness constraints that can combine relationships and attributes", - "update": "migration_required" + "description": "List of multi-element uniqueness constraints that can combine relationships and attributes" }, "documentation": { "anyOf": [ @@ -3271,36 +3287,73 @@ } ], "title": "Documentation", - "description": "Link to a documentation associated with this object, can be internal or external.", - "update": "allowed" + "description": "Link to a documentation associated with this object, can be internal or external." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the node/generic after loading the schema", + "default": "present" }, "attributes": { "items": { - "$ref": "#/components/schemas/AttributeSchema-Output" + "oneOf": [ + { + "$ref": "#/components/schemas/TextAttributeRead" + }, + { + "$ref": "#/components/schemas/NumberAttributeRead" + }, + { + "$ref": "#/components/schemas/ListAttributeRead" + }, + { + "$ref": "#/components/schemas/NumberPoolAttributeRead" + }, + { + "$ref": "#/components/schemas/GenericAttributeRead" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Any": "#/components/schemas/GenericAttributeRead", + "Bandwidth": "#/components/schemas/GenericAttributeRead", + "Boolean": "#/components/schemas/GenericAttributeRead", + "Checkbox": "#/components/schemas/GenericAttributeRead", + "Color": "#/components/schemas/GenericAttributeRead", + "DateTime": "#/components/schemas/GenericAttributeRead", + "Dropdown": "#/components/schemas/GenericAttributeRead", + "Email": "#/components/schemas/GenericAttributeRead", + "File": "#/components/schemas/GenericAttributeRead", + "HashedPassword": "#/components/schemas/GenericAttributeRead", + "ID": "#/components/schemas/GenericAttributeRead", + "IPHost": "#/components/schemas/GenericAttributeRead", + "IPNetwork": "#/components/schemas/GenericAttributeRead", + "JSON": "#/components/schemas/GenericAttributeRead", + "List": "#/components/schemas/ListAttributeRead", + "MacAddress": "#/components/schemas/GenericAttributeRead", + "Number": "#/components/schemas/NumberAttributeRead", + "NumberPool": "#/components/schemas/NumberPoolAttributeRead", + "Password": "#/components/schemas/GenericAttributeRead", + "Text": "#/components/schemas/TextAttributeRead", + "TextArea": "#/components/schemas/TextAttributeRead", + "URL": "#/components/schemas/GenericAttributeRead" + } + } }, "type": "array", "title": "Attributes", - "description": "Node attributes", - "update": "not_applicable" + "description": "Node attributes" }, "relationships": { "items": { - "$ref": "#/components/schemas/RelationshipSchema" + "$ref": "#/components/schemas/RelationshipSchemaRead" }, "type": "array", "title": "Relationships", - "description": "Node Relationships", - "update": "not_applicable" - }, - "inherit_from": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Inherit From", - "description": "List of Generic Kind that this profile is inheriting from" + "description": "Node Relationships" }, - "kind": { + "hash": { "anyOf": [ { "type": "string" @@ -3309,19 +3362,28 @@ "type": "null" } ], - "title": "Kind" + "title": "Hash", + "description": "Hash of the node computed by the server." }, - "hash": { + "inherit_from": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Inherit From", + "description": "List of Generic Kind that this profile is inheriting from" + }, + "kind": { "type": "string", - "title": "Hash" + "title": "Kind", + "readOnly": true } }, - "additionalProperties": false, "type": "object", "required": [ "name", "namespace", - "hash" + "kind" ], "title": "APIProfileSchema" }, @@ -3337,14 +3399,7 @@ } ], "title": "Id", - "description": "The ID of the node", - "update": "not_applicable" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "description": "Expected state of the node/generic after loading the schema", - "default": "present", - "update": "not_applicable" + "description": "The ID of the node" }, "name": { "type": "string", @@ -3352,8 +3407,7 @@ "minLength": 2, "pattern": "^[A-Z][a-zA-Z0-9]+$", "title": "Name", - "description": "Node name, must be unique within a namespace and must start with an uppercase letter.", - "update": "migration_required" + "description": "Node name, must be unique within a namespace and must start with an uppercase letter." }, "namespace": { "type": "string", @@ -3361,8 +3415,7 @@ "minLength": 3, "pattern": "^[A-Z][a-z0-9]+$", "title": "Namespace", - "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions.", - "update": "migration_required" + "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions." }, "description": { "anyOf": [ @@ -3375,8 +3428,7 @@ } ], "title": "Description", - "description": "Short description of the model, will be visible in the frontend.", - "update": "allowed" + "description": "Short description of the model, will be visible in the frontend." }, "label": { "anyOf": [ @@ -3389,14 +3441,12 @@ } ], "title": "Label", - "description": "Human friendly representation of the name/kind", - "update": "allowed" + "description": "Human friendly representation of the name/kind" }, "branch": { "$ref": "#/components/schemas/BranchSupportType", "description": "Type of branch support for the model.", - "default": "aware", - "update": "not_supported" + "default": "aware" }, "default_filter": { "anyOf": [ @@ -3409,8 +3459,7 @@ } ], "title": "Default Filter", - "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)", - "update": "allowed" + "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)" }, "human_friendly_id": { "anyOf": [ @@ -3425,8 +3474,7 @@ } ], "title": "Human Friendly Id", - "description": "Human friendly and unique identifier for the object.", - "update": "allowed" + "description": "Human friendly and unique identifier for the object." }, "display_label": { "anyOf": [ @@ -3438,15 +3486,13 @@ } ], "title": "Display Label", - "description": "Attribute or Jinja2 template to use to generate the display label", - "update": "allowed" + "description": "Attribute or Jinja2 template to use to generate the display label" }, "display_labels": { "anyOf": [ { "items": { - "type": "string", - "deprecationMessage": "display_labels are deprecated use display_label instead" + "type": "string" }, "type": "array" }, @@ -3455,8 +3501,7 @@ } ], "title": "Display Labels", - "description": "List of attributes to use to generate the display label (deprecated)", - "update": "allowed" + "description": "List of attributes to use to generate the display label (deprecated)" }, "include_in_menu": { "anyOf": [ @@ -3468,8 +3513,7 @@ } ], "title": "Include In Menu", - "description": "Defines if objects of this kind should be included in the menu.", - "update": "allowed" + "description": "Defines if objects of this kind should be included in the menu." }, "menu_placement": { "anyOf": [ @@ -3481,8 +3525,7 @@ } ], "title": "Menu Placement", - "description": "Defines where in the menu this object should be placed.", - "update": "allowed" + "description": "Defines where in the menu this object should be placed." }, "icon": { "anyOf": [ @@ -3494,8 +3537,7 @@ } ], "title": "Icon", - "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/", - "update": "allowed" + "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/" }, "order_by": { "anyOf": [ @@ -3510,8 +3552,7 @@ } ], "title": "Order By", - "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc.", - "update": "allowed" + "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc." }, "uniqueness_constraints": { "anyOf": [ @@ -3529,8 +3570,7 @@ } ], "title": "Uniqueness Constraints", - "description": "List of multi-element uniqueness constraints that can combine relationships and attributes", - "update": "migration_required" + "description": "List of multi-element uniqueness constraints that can combine relationships and attributes" }, "documentation": { "anyOf": [ @@ -3542,36 +3582,73 @@ } ], "title": "Documentation", - "description": "Link to a documentation associated with this object, can be internal or external.", - "update": "allowed" + "description": "Link to a documentation associated with this object, can be internal or external." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the node/generic after loading the schema", + "default": "present" }, "attributes": { "items": { - "$ref": "#/components/schemas/AttributeSchema-Output" + "oneOf": [ + { + "$ref": "#/components/schemas/TextAttributeRead" + }, + { + "$ref": "#/components/schemas/NumberAttributeRead" + }, + { + "$ref": "#/components/schemas/ListAttributeRead" + }, + { + "$ref": "#/components/schemas/NumberPoolAttributeRead" + }, + { + "$ref": "#/components/schemas/GenericAttributeRead" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Any": "#/components/schemas/GenericAttributeRead", + "Bandwidth": "#/components/schemas/GenericAttributeRead", + "Boolean": "#/components/schemas/GenericAttributeRead", + "Checkbox": "#/components/schemas/GenericAttributeRead", + "Color": "#/components/schemas/GenericAttributeRead", + "DateTime": "#/components/schemas/GenericAttributeRead", + "Dropdown": "#/components/schemas/GenericAttributeRead", + "Email": "#/components/schemas/GenericAttributeRead", + "File": "#/components/schemas/GenericAttributeRead", + "HashedPassword": "#/components/schemas/GenericAttributeRead", + "ID": "#/components/schemas/GenericAttributeRead", + "IPHost": "#/components/schemas/GenericAttributeRead", + "IPNetwork": "#/components/schemas/GenericAttributeRead", + "JSON": "#/components/schemas/GenericAttributeRead", + "List": "#/components/schemas/ListAttributeRead", + "MacAddress": "#/components/schemas/GenericAttributeRead", + "Number": "#/components/schemas/NumberAttributeRead", + "NumberPool": "#/components/schemas/NumberPoolAttributeRead", + "Password": "#/components/schemas/GenericAttributeRead", + "Text": "#/components/schemas/TextAttributeRead", + "TextArea": "#/components/schemas/TextAttributeRead", + "URL": "#/components/schemas/GenericAttributeRead" + } + } }, "type": "array", "title": "Attributes", - "description": "Node attributes", - "update": "not_applicable" + "description": "Node attributes" }, "relationships": { "items": { - "$ref": "#/components/schemas/RelationshipSchema" + "$ref": "#/components/schemas/RelationshipSchemaRead" }, "type": "array", "title": "Relationships", - "description": "Node Relationships", - "update": "not_applicable" - }, - "inherit_from": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Inherit From", - "description": "List of Generic Kind that this template is inheriting from" + "description": "Node Relationships" }, - "kind": { + "hash": { "anyOf": [ { "type": "string" @@ -3580,19 +3657,28 @@ "type": "null" } ], - "title": "Kind" + "title": "Hash", + "description": "Hash of the node computed by the server." }, - "hash": { + "inherit_from": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Inherit From", + "description": "List of Generic Kind that this template is inheriting from" + }, + "kind": { "type": "string", - "title": "Hash" + "title": "Kind", + "readOnly": true } }, - "additionalProperties": false, "type": "object", "required": [ "name", "namespace", - "hash" + "kind" ], "title": "APITemplateSchema" }, @@ -3694,31 +3780,42 @@ ], "title": "ArtifactTarget" }, - "AttributeParameters": { + "AttributeParametersRead": { + "properties": {}, + "type": "object", + "title": "AttributeParametersRead" + }, + "AttributeParametersWrite": { + "properties": {}, + "additionalProperties": false, + "type": "object", + "title": "AttributeParametersWrite" + }, + "Body_upload_file_api_storage_upload_file_post": { "properties": { - "id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Id" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "default": "present" + "file": { + "type": "string", + "contentMediaType": "application/octet-stream", + "title": "File" } }, - "additionalProperties": false, "type": "object", - "title": "AttributeParameters" + "required": [ + "file" + ], + "title": "Body_upload_file_api_storage_upload_file_post" }, - "AttributeSchema-Input": { + "BranchDiffArtifact": { "properties": { + "branch": { + "type": "string", + "title": "Branch" + }, "id": { + "type": "string", + "title": "Id" + }, + "display_label": { "anyOf": [ { "type": "string" @@ -3727,271 +3824,101 @@ "type": "null" } ], - "title": "Id", - "description": "The ID of the attribute", - "update": "not_applicable" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "description": "Expected state of the attribute after loading the schema", - "default": "present", - "update": "not_applicable" - }, - "name": { - "type": "string", - "maxLength": 64, - "minLength": 3, - "pattern": "^[a-z0-9\\_]+$", - "title": "Name", - "description": "Attribute name, must be unique within a model and must be all lowercase.", - "update": "migration_required" + "title": "Display Label" }, - "kind": { - "type": "string", - "title": "Kind", - "description": "Defines the type of the attribute.", - "update": "migration_required" + "action": { + "$ref": "#/components/schemas/DiffAction" }, - "enum": { + "target": { "anyOf": [ { - "items": {}, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Enum", - "description": "Define a list of valid values for the attribute.", - "update": "validate_constraint" - }, - "computed_attribute": { - "anyOf": [ - { - "$ref": "#/components/schemas/ComputedAttribute-Input" - }, - { - "type": "null" - } - ], - "description": "Defines how the value of this attribute will be populated.", - "update": "allowed" - }, - "choices": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/DropdownChoice" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Choices", - "description": "Define a list of valid choices for a dropdown attribute.", - "update": "validate_constraint" - }, - "regex": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Regex", - "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)", - "update": "validate_constraint" - }, - "max_length": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Max Length", - "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)", - "update": "validate_constraint" - }, - "min_length": { - "anyOf": [ - { - "type": "integer" + "$ref": "#/components/schemas/ArtifactTarget" }, { "type": "null" } - ], - "title": "Min Length", - "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)", - "update": "validate_constraint" + ] }, - "label": { + "item_new": { "anyOf": [ { - "type": "string", - "maxLength": 64 + "$ref": "#/components/schemas/BranchDiffArtifactStorage" }, { "type": "null" } - ], - "title": "Label", - "description": "Human friendly representation of the name. Will be autogenerated if not provided", - "update": "allowed" + ] }, - "description": { + "item_previous": { "anyOf": [ { - "type": "string", - "maxLength": 128 + "$ref": "#/components/schemas/BranchDiffArtifactStorage" }, { "type": "null" } - ], - "title": "Description", - "description": "Short description of the attribute.", - "update": "allowed" - }, - "read_only": { - "type": "boolean", - "title": "Read Only", - "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", - "default": false, - "update": "migration_required" - }, - "unique": { - "type": "boolean", - "title": "Unique", - "description": "Indicate if the value of this attribute must be unique in the database for a given model.", - "default": false, - "update": "migration_required" - }, - "optional": { - "type": "boolean", - "title": "Optional", - "description": "Indicate if this attribute is mandatory or optional.", - "default": false, - "update": "migration_required" + ] + } + }, + "type": "object", + "required": [ + "branch", + "id", + "action" + ], + "title": "BranchDiffArtifact" + }, + "BranchDiffArtifactStorage": { + "properties": { + "storage_id": { + "type": "string", + "title": "Storage Id" }, + "checksum": { + "type": "string", + "title": "Checksum" + } + }, + "type": "object", + "required": [ + "storage_id", + "checksum" + ], + "title": "BranchDiffArtifactStorage" + }, + "BranchDiffFile": { + "properties": { "branch": { - "anyOf": [ - { - "$ref": "#/components/schemas/BranchSupportType" - }, - { - "type": "null" - } - ], - "description": "Type of branch support for the attribute, if not defined it will be inherited from the node.", - "update": "not_supported" - }, - "order_weight": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Order Weight", - "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first.", - "update": "allowed" - }, - "ordered": { - "type": "boolean", - "title": "Ordered", - "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", - "default": true, - "update": "allowed" - }, - "default_value": { - "anyOf": [ - {}, - { - "type": "null" - } - ], - "title": "Default Value", - "description": "Default value of the attribute.", - "update": "allowed" - }, - "inherited": { - "type": "boolean", - "title": "Inherited", - "description": "Internal value to indicate if the attribute was inherited from a Generic node.", - "default": false, - "update": "not_applicable" - }, - "allow_override": { - "$ref": "#/components/schemas/AllowOverrideType", - "description": "Type of allowed override for the attribute.", - "default": "any", - "update": "allowed" - }, - "parameters": { - "anyOf": [ - { - "$ref": "#/components/schemas/AttributeParameters" - }, - { - "$ref": "#/components/schemas/ListAttributeParameters" - }, - { - "$ref": "#/components/schemas/TextAttributeParameters" - }, - { - "$ref": "#/components/schemas/NumberAttributeParameters" - }, - { - "$ref": "#/components/schemas/NumberPoolParameters" - } - ], - "title": "Parameters", - "description": "Extra parameters specific to this kind of attribute", - "update": "validate_constraint" + "type": "string", + "title": "Branch" }, - "deprecation": { - "anyOf": [ - { - "type": "string", - "maxLength": 128 - }, - { - "type": "null" - } - ], - "title": "Deprecation", - "description": "Mark attribute as deprecated and provide a user-friendly message to display", - "update": "allowed" + "location": { + "type": "string", + "title": "Location" }, - "display": { - "$ref": "#/components/schemas/SchemaAttributeDisplay", - "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", - "default": "default", - "update": "allowed" + "action": { + "$ref": "#/components/schemas/DiffAction" } }, - "additionalProperties": false, "type": "object", "required": [ - "name", - "kind" + "branch", + "location", + "action" ], - "title": "AttributeSchema" + "title": "BranchDiffFile" }, - "AttributeSchema-Output": { + "BranchDiffRepository": { "properties": { + "branch": { + "type": "string", + "title": "Branch" + }, "id": { + "type": "string", + "title": "Id" + }, + "display_name": { "anyOf": [ { "type": "string" @@ -4000,240 +3927,514 @@ "type": "null" } ], - "title": "Id", - "description": "The ID of the attribute", - "update": "not_applicable" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "description": "Expected state of the attribute after loading the schema", - "default": "present", - "update": "not_applicable" + "title": "Display Name" }, - "name": { + "commit_from": { "type": "string", - "maxLength": 64, - "minLength": 3, - "pattern": "^[a-z0-9\\_]+$", - "title": "Name", - "description": "Attribute name, must be unique within a model and must be all lowercase.", - "update": "migration_required" + "title": "Commit From" }, - "kind": { + "commit_to": { "type": "string", - "title": "Kind", - "description": "Defines the type of the attribute.", - "update": "migration_required" - }, - "enum": { - "anyOf": [ - { - "items": {}, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Enum", - "description": "Define a list of valid values for the attribute.", - "update": "validate_constraint" - }, - "computed_attribute": { - "anyOf": [ - { - "$ref": "#/components/schemas/ComputedAttribute-Output" - }, - { - "type": "null" - } - ], - "description": "Defines how the value of this attribute will be populated.", - "update": "allowed" + "title": "Commit To" }, - "choices": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/DropdownChoice" - }, - "type": "array" + "files": { + "items": { + "$ref": "#/components/schemas/BranchDiffFile" + }, + "type": "array", + "title": "Files" + } + }, + "type": "object", + "required": [ + "branch", + "id", + "commit_from", + "commit_to" + ], + "title": "BranchDiffRepository" + }, + "BranchSupportType": { + "type": "string", + "enum": [ + "aware", + "agnostic", + "local" + ], + "title": "BranchSupportType" + }, + "ComputedAttributeJinja2Read": { + "properties": { + "kind": { + "type": "string", + "const": "Jinja2", + "title": "Kind", + "description": "Defines how the value of the attribute is computed." + }, + "jinja2_template": { + "type": "string", + "title": "Jinja2 Template", + "description": "Jinja2 template used to compute the value, required when kind is Jinja2." + } + }, + "type": "object", + "required": [ + "kind", + "jinja2_template" + ], + "title": "ComputedAttributeJinja2Read" + }, + "ComputedAttributeJinja2Write": { + "properties": { + "kind": { + "type": "string", + "const": "Jinja2", + "title": "Kind", + "description": "Defines how the value of the attribute is computed." + }, + "jinja2_template": { + "type": "string", + "title": "Jinja2 Template", + "description": "Jinja2 template used to compute the value, required when kind is Jinja2." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "kind", + "jinja2_template" + ], + "title": "ComputedAttributeJinja2Write" + }, + "ComputedAttributeTransformPythonRead": { + "properties": { + "kind": { + "type": "string", + "const": "TransformPython", + "title": "Kind", + "description": "Defines how the value of the attribute is computed." + }, + "transform": { + "type": "string", + "title": "Transform", + "description": "Python transform name or ID, required when kind is TransformPython." + } + }, + "type": "object", + "required": [ + "kind", + "transform" + ], + "title": "ComputedAttributeTransformPythonRead" + }, + "ComputedAttributeTransformPythonWrite": { + "properties": { + "kind": { + "type": "string", + "const": "TransformPython", + "title": "Kind", + "description": "Defines how the value of the attribute is computed." + }, + "transform": { + "type": "string", + "title": "Transform", + "description": "Python transform name or ID, required when kind is TransformPython." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "kind", + "transform" + ], + "title": "ComputedAttributeTransformPythonWrite" + }, + "ComputedAttributeUserRead": { + "properties": { + "kind": { + "type": "string", + "const": "User", + "title": "Kind", + "description": "Defines how the value of the attribute is computed." + } + }, + "type": "object", + "required": [ + "kind" + ], + "title": "ComputedAttributeUserRead" + }, + "ComputedAttributeUserWrite": { + "properties": { + "kind": { + "type": "string", + "const": "User", + "title": "Kind", + "description": "Defines how the value of the attribute is computed." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "kind" + ], + "title": "ComputedAttributeUserWrite" + }, + "ConfigAPI": { + "properties": { + "main": { + "$ref": "#/components/schemas/MainSettings" + }, + "logging": { + "$ref": "#/components/schemas/LoggingSettings" + }, + "analytics": { + "$ref": "#/components/schemas/AnalyticsSettings" + }, + "experimental_features": { + "$ref": "#/components/schemas/ExperimentalFeaturesSettings" + }, + "sso": { + "$ref": "#/components/schemas/SSOInfo" + }, + "ldap": { + "$ref": "#/components/schemas/LDAPInfo" + }, + "installation_type": { + "type": "string", + "title": "Installation Type" + }, + "policy": { + "$ref": "#/components/schemas/PolicySettings" + } + }, + "type": "object", + "required": [ + "main", + "logging", + "analytics", + "experimental_features", + "sso", + "ldap", + "installation_type", + "policy" + ], + "title": "ConfigAPI" + }, + "DiffAction": { + "type": "string", + "enum": [ + "added", + "removed", + "updated", + "unchanged" + ], + "title": "DiffAction" + }, + "DropdownChoiceRead": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the choice, must be unique within the dropdown." + }, + "description": { + "anyOf": [ + { + "type": "string" }, { "type": "null" } ], - "title": "Choices", - "description": "Define a list of valid choices for a dropdown attribute.", - "update": "validate_constraint" + "title": "Description", + "description": "Description of the choice." }, - "regex": { + "color": { "anyOf": [ { - "type": "string" + "type": "string", + "pattern": "#[0-9a-fA-F]{6}\\b" }, { "type": "null" } ], - "title": "Regex", - "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)", - "update": "validate_constraint" + "title": "Color", + "description": "Color of the choice, must be a valid HTML color code." }, - "max_length": { + "label": { "anyOf": [ { - "type": "integer" + "type": "string" }, { "type": "null" } ], - "title": "Max Length", - "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)", - "update": "validate_constraint" + "title": "Label", + "description": "Human friendly representation of the choice." + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "DropdownChoiceRead" + }, + "DropdownChoiceWrite": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the choice, must be unique within the dropdown." }, - "min_length": { + "description": { "anyOf": [ { - "type": "integer" + "type": "string" }, { "type": "null" } ], - "title": "Min Length", - "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)", - "update": "validate_constraint" + "title": "Description", + "description": "Description of the choice." }, - "label": { + "color": { "anyOf": [ { "type": "string", - "maxLength": 64 + "pattern": "#[0-9a-fA-F]{6}\\b" }, { "type": "null" } ], - "title": "Label", - "description": "Human friendly representation of the name. Will be autogenerated if not provided", - "update": "allowed" + "title": "Color", + "description": "Color of the choice, must be a valid HTML color code." }, - "description": { + "label": { "anyOf": [ { - "type": "string", - "maxLength": 128 + "type": "string" }, { "type": "null" } ], - "title": "Description", - "description": "Short description of the attribute.", - "update": "allowed" - }, - "read_only": { - "type": "boolean", - "title": "Read Only", - "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", - "default": false, - "update": "migration_required" - }, - "unique": { - "type": "boolean", - "title": "Unique", - "description": "Indicate if the value of this attribute must be unique in the database for a given model.", - "default": false, - "update": "migration_required" + "title": "Label", + "description": "Human friendly representation of the choice." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ], + "title": "DropdownChoiceWrite" + }, + "EnterpriseRequiredResponse": { + "properties": { + "error_code": { + "type": "string", + "title": "Error Code", + "default": "ENTERPRISE_REQUIRED" }, - "optional": { - "type": "boolean", - "title": "Optional", - "description": "Indicate if this attribute is mandatory or optional.", - "default": false, - "update": "migration_required" + "feature": { + "type": "string", + "title": "Feature" }, - "branch": { + "message": { "anyOf": [ { - "$ref": "#/components/schemas/BranchSupportType" + "type": "string" }, { "type": "null" } ], - "description": "Type of branch support for the attribute, if not defined it will be inherited from the node.", - "update": "not_supported" - }, - "order_weight": { - "anyOf": [ + "title": "Message" + } + }, + "type": "object", + "required": [ + "feature" + ], + "title": "EnterpriseRequiredResponse" + }, + "ExperimentalFeaturesSettings": { + "properties": { + "graphql_enums": { + "type": "boolean", + "title": "Graphql Enums", + "default": false + }, + "value_db_index": { + "type": "boolean", + "title": "Value Db Index", + "default": false, + "deprecated": true + } + }, + "additionalProperties": false, + "type": "object", + "title": "ExperimentalFeaturesSettings" + }, + "GenericAttributeRead": { + "properties": { + "id": { + "anyOf": [ { - "type": "integer" + "type": "string" }, { "type": "null" } ], - "title": "Order Weight", - "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first.", - "update": "allowed" + "title": "Id", + "description": "The ID of the attribute" }, - "ordered": { - "type": "boolean", - "title": "Ordered", - "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", - "default": true, - "update": "allowed" + "name": { + "type": "string", + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." }, - "default_value": { + "kind": { + "type": "string", + "enum": [ + "ID", + "Dropdown", + "DateTime", + "Email", + "Password", + "HashedPassword", + "URL", + "File", + "MacAddress", + "Color", + "Bandwidth", + "IPHost", + "IPNetwork", + "Boolean", + "Checkbox", + "JSON", + "Any" + ], + "title": "Kind", + "description": "Defines the type of the attribute." + }, + "enum": { "anyOf": [ - {}, + { + "items": {}, + "type": "array" + }, { "type": "null" } ], - "title": "Default Value", - "description": "Default value of the attribute.", - "update": "allowed" + "title": "Enum", + "description": "Define a list of valid values for the attribute." }, - "inherited": { - "type": "boolean", - "title": "Inherited", - "description": "Internal value to indicate if the attribute was inherited from a Generic node.", - "default": false, - "update": "not_applicable" + "computed_attribute": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserRead" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Read" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonRead" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Read", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonRead", + "User": "#/components/schemas/ComputedAttributeUserRead" + } + } + }, + { + "type": "null" + } + ], + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." }, - "allow_override": { - "$ref": "#/components/schemas/AllowOverrideType", - "description": "Type of allowed override for the attribute.", - "default": "any", - "update": "allowed" + "choices": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/DropdownChoiceRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." }, - "parameters": { + "regex": { "anyOf": [ { - "$ref": "#/components/schemas/AttributeParameters" + "type": "string" }, { - "$ref": "#/components/schemas/ListAttributeParameters" + "type": "null" + } + ], + "title": "Regex", + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" + }, + "max_length": { + "anyOf": [ + { + "type": "integer" }, { - "$ref": "#/components/schemas/TextAttributeParameters" + "type": "null" + } + ], + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" + }, + "min_length": { + "anyOf": [ + { + "type": "integer" }, { - "$ref": "#/components/schemas/NumberAttributeParameters" + "type": "null" + } + ], + "title": "Min Length", + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" + }, + "label": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 }, { - "$ref": "#/components/schemas/NumberPoolParameters" + "type": "null" } ], - "title": "Parameters", - "description": "Extra parameters specific to this kind of attribute", - "update": "validate_constraint" + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" }, - "deprecation": { + "description": { "anyOf": [ { "type": "string", @@ -4243,153 +4444,122 @@ "type": "null" } ], - "title": "Deprecation", - "description": "Mark attribute as deprecated and provide a user-friendly message to display", - "update": "allowed" + "title": "Description", + "description": "Short description of the attribute." }, - "display": { - "$ref": "#/components/schemas/SchemaAttributeDisplay", - "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", - "default": "default", - "update": "allowed" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "name", - "kind" - ], - "title": "AttributeSchema" - }, - "Body_upload_file_api_storage_upload_file_post": { - "properties": { - "file": { - "type": "string", - "contentMediaType": "application/octet-stream", - "title": "File" - } - }, - "type": "object", - "required": [ - "file" - ], - "title": "Body_upload_file_api_storage_upload_file_post" - }, - "BranchDiffArtifact": { - "properties": { - "branch": { - "type": "string", - "title": "Branch" + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", + "default": false }, - "id": { - "type": "string", - "title": "Id" + "unique": { + "type": "boolean", + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false }, - "display_label": { + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/BranchSupportType" }, { "type": "null" } ], - "title": "Display Label" - }, - "action": { - "$ref": "#/components/schemas/DiffAction" + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." }, - "target": { + "order_weight": { "anyOf": [ { - "$ref": "#/components/schemas/ArtifactTarget" + "type": "integer" }, { "type": "null" } - ] + ], + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." }, - "item_new": { + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true + }, + "default_value": { "anyOf": [ + {}, { - "$ref": "#/components/schemas/BranchDiffArtifactStorage" + "type": "null" + } + ], + "title": "Default Value", + "description": "Default value of the attribute." + }, + "inherited": { + "type": "boolean", + "title": "Inherited", + "description": "Internal value to indicate if the attribute was inherited from a Generic node.", + "default": false + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 }, { "type": "null" } - ] + ], + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" }, - "item_previous": { + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + }, + "parameters": { "anyOf": [ { - "$ref": "#/components/schemas/BranchDiffArtifactStorage" + "$ref": "#/components/schemas/AttributeParametersRead" }, { "type": "null" } - ] + ], + "description": "Extra parameters specific to this kind of attribute" } }, "type": "object", "required": [ - "branch", - "id", - "action" + "name", + "kind" ], - "title": "BranchDiffArtifact" + "title": "GenericAttributeRead" }, - "BranchDiffArtifactStorage": { + "GenericAttributeWrite": { "properties": { - "storage_id": { - "type": "string", - "title": "Storage Id" - }, - "checksum": { - "type": "string", - "title": "Checksum" - } - }, - "type": "object", - "required": [ - "storage_id", - "checksum" - ], - "title": "BranchDiffArtifactStorage" - }, - "BranchDiffFile": { - "properties": { - "branch": { - "type": "string", - "title": "Branch" - }, - "location": { - "type": "string", - "title": "Location" - }, - "action": { - "$ref": "#/components/schemas/DiffAction" - } - }, - "type": "object", - "required": [ - "branch", - "location", - "action" - ], - "title": "BranchDiffFile" - }, - "BranchDiffRepository": { - "properties": { - "branch": { - "type": "string", - "title": "Branch" - }, "id": { - "type": "string", - "title": "Id" - }, - "display_name": { "anyOf": [ { "type": "string" @@ -4398,203 +4568,100 @@ "type": "null" } ], - "title": "Display Name" + "title": "Id", + "description": "The ID of the attribute" }, - "commit_from": { + "name": { "type": "string", - "title": "Commit From" + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." }, - "commit_to": { + "kind": { "type": "string", - "title": "Commit To" - }, - "files": { - "items": { - "$ref": "#/components/schemas/BranchDiffFile" - }, - "type": "array", - "title": "Files" - } - }, - "type": "object", - "required": [ - "branch", - "id", - "commit_from", - "commit_to" - ], - "title": "BranchDiffRepository" - }, - "BranchSupportType": { - "type": "string", - "enum": [ - "aware", - "agnostic", - "local" - ], - "title": "BranchSupportType" - }, - "ComputedAttribute-Input": { - "allOf": [ - { - "if": { - "properties": { - "kind": { - "const": "Jinja2" - } - } - }, - "then": { - "properties": { - "jinja2_template": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "jinja2_template" - ] - } + "enum": [ + "ID", + "Dropdown", + "DateTime", + "Email", + "Password", + "HashedPassword", + "URL", + "File", + "MacAddress", + "Color", + "Bandwidth", + "IPHost", + "IPNetwork", + "Boolean", + "Checkbox", + "JSON", + "Any" + ], + "title": "Kind", + "description": "Defines the type of the attribute." }, - { - "if": { - "properties": { - "kind": { - "const": "TransformPython" - } - } - }, - "then": { - "properties": { - "transform": { - "type": "string", - "minLength": 1 - } - }, - "required": [ - "transform" - ] - } - } - ], - "properties": { - "id": { + "enum": { "anyOf": [ { - "type": "string" + "items": {}, + "type": "array" }, { "type": "null" } ], - "title": "Id" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "default": "present" - }, - "kind": { - "$ref": "#/components/schemas/ComputedAttributeKind" + "title": "Enum", + "description": "Define a list of valid values for the attribute." }, - "jinja2_template": { + "computed_attribute": { "anyOf": [ { - "type": "string" + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserWrite" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Write" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonWrite" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Write", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonWrite", + "User": "#/components/schemas/ComputedAttributeUserWrite" + } + } }, { "type": "null" } ], - "title": "Jinja2 Template", - "description": "The Jinja2 template in string format, required when assignment_type=jinja2" + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." }, - "transform": { + "choices": { "anyOf": [ { - "type": "string" + "items": { + "$ref": "#/components/schemas/DropdownChoiceWrite" + }, + "type": "array" }, { "type": "null" } ], - "title": "Transform", - "description": "The Python Transform name or ID, required when assignment_type=transform" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "kind" - ], - "title": "ComputedAttribute" - }, - "ComputedAttribute-Output": { - "additionalProperties": true, - "type": "object" - }, - "ComputedAttributeKind": { - "type": "string", - "enum": [ - "User", - "Jinja2", - "TransformPython" - ], - "title": "ComputedAttributeKind" - }, - "ConfigAPI": { - "properties": { - "main": { - "$ref": "#/components/schemas/MainSettings" - }, - "logging": { - "$ref": "#/components/schemas/LoggingSettings" - }, - "analytics": { - "$ref": "#/components/schemas/AnalyticsSettings" - }, - "experimental_features": { - "$ref": "#/components/schemas/ExperimentalFeaturesSettings" - }, - "sso": { - "$ref": "#/components/schemas/SSOInfo" - }, - "ldap": { - "$ref": "#/components/schemas/LDAPInfo" - }, - "installation_type": { - "type": "string", - "title": "Installation Type" + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." }, - "policy": { - "$ref": "#/components/schemas/PolicySettings" - } - }, - "type": "object", - "required": [ - "main", - "logging", - "analytics", - "experimental_features", - "sso", - "ldap", - "installation_type", - "policy" - ], - "title": "ConfigAPI" - }, - "DiffAction": { - "type": "string", - "enum": [ - "added", - "removed", - "updated", - "unchanged" - ], - "title": "DiffAction" - }, - "DropdownChoice": { - "properties": { - "id": { + "regex": { "anyOf": [ { "type": "string" @@ -4603,105 +4670,165 @@ "type": "null" } ], - "title": "Id" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "default": "present" - }, - "name": { - "type": "string", - "title": "Name" + "title": "Regex", + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" }, - "description": { + "max_length": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Description" + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" }, - "color": { + "min_length": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Color" + "title": "Min Length", + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" }, "label": { "anyOf": [ { - "type": "string" + "type": "string", + "maxLength": 64 }, { "type": "null" } ], - "title": "Label" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "name" - ], - "title": "DropdownChoice" - }, - "EnterpriseRequiredResponse": { - "properties": { - "error_code": { - "type": "string", - "title": "Error Code", - "default": "ENTERPRISE_REQUIRED" - }, - "feature": { - "type": "string", - "title": "Feature" + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" }, - "message": { + "description": { "anyOf": [ { - "type": "string" + "type": "string", + "maxLength": 128 }, { "type": "null" } ], - "title": "Message" - } - }, - "type": "object", - "required": [ - "feature" - ], - "title": "EnterpriseRequiredResponse" - }, - "ExperimentalFeaturesSettings": { - "properties": { - "graphql_enums": { + "title": "Description", + "description": "Short description of the attribute." + }, + "read_only": { "type": "boolean", - "title": "Graphql Enums", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", "default": false }, - "value_db_index": { + "unique": { "type": "boolean", - "title": "Value Db Index", - "default": false, - "deprecated": true + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { + "anyOf": [ + { + "$ref": "#/components/schemas/BranchSupportType" + }, + { + "type": "null" + } + ], + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." + }, + "order_weight": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." + }, + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true + }, + "default_value": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Default Value", + "description": "Default value of the attribute." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" + }, + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + }, + "parameters": { + "anyOf": [ + { + "$ref": "#/components/schemas/AttributeParametersWrite" + }, + { + "type": "null" + } + ], + "description": "Extra parameters specific to this kind of attribute" } }, "additionalProperties": false, "type": "object", - "title": "ExperimentalFeaturesSettings" + "required": [ + "name", + "kind" + ], + "title": "GenericAttributeWrite" }, - "GenericSchema": { + "GenericSchemaWrite": { "properties": { "id": { "anyOf": [ @@ -4713,14 +4840,7 @@ } ], "title": "Id", - "description": "The ID of the node", - "update": "not_applicable" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "description": "Expected state of the node/generic after loading the schema", - "default": "present", - "update": "not_applicable" + "description": "The ID of the node" }, "name": { "type": "string", @@ -4728,8 +4848,7 @@ "minLength": 2, "pattern": "^[A-Z][a-zA-Z0-9]+$", "title": "Name", - "description": "Node name, must be unique within a namespace and must start with an uppercase letter.", - "update": "migration_required" + "description": "Node name, must be unique within a namespace and must start with an uppercase letter." }, "namespace": { "type": "string", @@ -4737,8 +4856,7 @@ "minLength": 3, "pattern": "^[A-Z][a-z0-9]+$", "title": "Namespace", - "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions.", - "update": "migration_required" + "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions." }, "description": { "anyOf": [ @@ -4751,8 +4869,7 @@ } ], "title": "Description", - "description": "Short description of the model, will be visible in the frontend.", - "update": "allowed" + "description": "Short description of the model, will be visible in the frontend." }, "label": { "anyOf": [ @@ -4765,14 +4882,12 @@ } ], "title": "Label", - "description": "Human friendly representation of the name/kind", - "update": "allowed" + "description": "Human friendly representation of the name/kind" }, "branch": { "$ref": "#/components/schemas/BranchSupportType", "description": "Type of branch support for the model.", - "default": "aware", - "update": "not_supported" + "default": "aware" }, "default_filter": { "anyOf": [ @@ -4785,8 +4900,7 @@ } ], "title": "Default Filter", - "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)", - "update": "allowed" + "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)" }, "human_friendly_id": { "anyOf": [ @@ -4801,8 +4915,7 @@ } ], "title": "Human Friendly Id", - "description": "Human friendly and unique identifier for the object.", - "update": "allowed" + "description": "Human friendly and unique identifier for the object." }, "display_label": { "anyOf": [ @@ -4814,15 +4927,13 @@ } ], "title": "Display Label", - "description": "Attribute or Jinja2 template to use to generate the display label", - "update": "allowed" + "description": "Attribute or Jinja2 template to use to generate the display label" }, "display_labels": { "anyOf": [ { "items": { - "type": "string", - "deprecationMessage": "display_labels are deprecated use display_label instead" + "type": "string" }, "type": "array" }, @@ -4831,8 +4942,7 @@ } ], "title": "Display Labels", - "description": "List of attributes to use to generate the display label (deprecated)", - "update": "allowed" + "description": "List of attributes to use to generate the display label (deprecated)" }, "include_in_menu": { "anyOf": [ @@ -4844,8 +4954,7 @@ } ], "title": "Include In Menu", - "description": "Defines if objects of this kind should be included in the menu.", - "update": "allowed" + "description": "Defines if objects of this kind should be included in the menu." }, "menu_placement": { "anyOf": [ @@ -4857,8 +4966,7 @@ } ], "title": "Menu Placement", - "description": "Defines where in the menu this object should be placed.", - "update": "allowed" + "description": "Defines where in the menu this object should be placed." }, "icon": { "anyOf": [ @@ -4870,8 +4978,7 @@ } ], "title": "Icon", - "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/", - "update": "allowed" + "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/" }, "order_by": { "anyOf": [ @@ -4886,8 +4993,7 @@ } ], "title": "Order By", - "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc.", - "update": "allowed" + "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc." }, "uniqueness_constraints": { "anyOf": [ @@ -4905,8 +5011,7 @@ } ], "title": "Uniqueness Constraints", - "description": "List of multi-element uniqueness constraints that can combine relationships and attributes", - "update": "migration_required" + "description": "List of multi-element uniqueness constraints that can combine relationships and attributes" }, "documentation": { "anyOf": [ @@ -4918,49 +5023,83 @@ } ], "title": "Documentation", - "description": "Link to a documentation associated with this object, can be internal or external.", - "update": "allowed" + "description": "Link to a documentation associated with this object, can be internal or external." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the node/generic after loading the schema", + "default": "present" }, "attributes": { "items": { - "$ref": "#/components/schemas/AttributeSchema-Input" + "oneOf": [ + { + "$ref": "#/components/schemas/TextAttributeWrite" + }, + { + "$ref": "#/components/schemas/NumberAttributeWrite" + }, + { + "$ref": "#/components/schemas/ListAttributeWrite" + }, + { + "$ref": "#/components/schemas/NumberPoolAttributeWrite" + }, + { + "$ref": "#/components/schemas/GenericAttributeWrite" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Any": "#/components/schemas/GenericAttributeWrite", + "Bandwidth": "#/components/schemas/GenericAttributeWrite", + "Boolean": "#/components/schemas/GenericAttributeWrite", + "Checkbox": "#/components/schemas/GenericAttributeWrite", + "Color": "#/components/schemas/GenericAttributeWrite", + "DateTime": "#/components/schemas/GenericAttributeWrite", + "Dropdown": "#/components/schemas/GenericAttributeWrite", + "Email": "#/components/schemas/GenericAttributeWrite", + "File": "#/components/schemas/GenericAttributeWrite", + "HashedPassword": "#/components/schemas/GenericAttributeWrite", + "ID": "#/components/schemas/GenericAttributeWrite", + "IPHost": "#/components/schemas/GenericAttributeWrite", + "IPNetwork": "#/components/schemas/GenericAttributeWrite", + "JSON": "#/components/schemas/GenericAttributeWrite", + "List": "#/components/schemas/ListAttributeWrite", + "MacAddress": "#/components/schemas/GenericAttributeWrite", + "Number": "#/components/schemas/NumberAttributeWrite", + "NumberPool": "#/components/schemas/NumberPoolAttributeWrite", + "Password": "#/components/schemas/GenericAttributeWrite", + "Text": "#/components/schemas/TextAttributeWrite", + "TextArea": "#/components/schemas/TextAttributeWrite", + "URL": "#/components/schemas/GenericAttributeWrite" + } + } }, "type": "array", "title": "Attributes", - "description": "Node attributes", - "update": "not_applicable" + "description": "Node attributes" }, "relationships": { "items": { - "$ref": "#/components/schemas/RelationshipSchema" + "$ref": "#/components/schemas/RelationshipSchemaWrite" }, "type": "array", "title": "Relationships", - "description": "Node Relationships", - "update": "not_applicable" + "description": "Node Relationships" }, "hierarchical": { "type": "boolean", "title": "Hierarchical", "description": "Defines if the Generic support the hierarchical mode.", - "default": false, - "update": "validate_constraint" + "default": false }, "generate_profile": { "type": "boolean", "title": "Generate Profile", "description": "Indicate if a profile schema should be generated for this schema", - "default": true, - "update": "validate_constraint" - }, - "used_by": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Used By", - "description": "List of Nodes that are referencing this Generic", - "update": "not_applicable" + "default": true }, "restricted_namespaces": { "anyOf": [ @@ -4975,8 +5114,7 @@ } ], "title": "Restricted Namespaces", - "description": "Nodes inheriting from this Generic schema must belong to one of the listed namespaces", - "update": "allowed" + "description": "Nodes inheriting from this Generic schema must belong to one of the listed namespaces" } }, "additionalProperties": false, @@ -4985,8 +5123,7 @@ "name", "namespace" ], - "title": "GenericSchema", - "description": "A Generic can be either an Interface or a Union depending if there are some Attributes or Relationships defined." + "title": "GenericSchemaWrite" }, "HTTPValidationError": { "properties": { @@ -5050,14 +5187,6 @@ "type": "object", "title": "HashableModelDiff" }, - "HashableModelState": { - "type": "string", - "enum": [ - "present", - "absent" - ], - "title": "HashableModelState" - }, "InfoAPI": { "properties": { "deployment_id": { @@ -5288,9 +5417,9 @@ "type": "object", "title": "LDAPInfo" }, - "ListAttributeParameters": { + "ListAttributeParametersRead": { "properties": { - "id": { + "regex": { "anyOf": [ { "type": "string" @@ -5299,12 +5428,15 @@ "type": "null" } ], - "title": "Id" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "default": "present" - }, + "title": "Regex", + "description": "Regular expression that each list item value must match if defined" + } + }, + "type": "object", + "title": "ListAttributeParametersRead" + }, + "ListAttributeParametersWrite": { + "properties": { "regex": { "anyOf": [ { @@ -5315,38 +5447,16 @@ } ], "title": "Regex", - "description": "Regular expression that each list item value must match if defined", - "update": "validate_constraint" - } - }, - "additionalProperties": false, - "type": "object", - "title": "ListAttributeParameters", - "description": "Parameters for List attributes supporting regex validation on list items." - }, - "LoggingSettings": { - "properties": { - "remote": { - "$ref": "#/components/schemas/RemoteLoggingSettings", - "default": { - "enable": false - } + "description": "Regular expression that each list item value must match if defined" } }, "additionalProperties": false, "type": "object", - "title": "LoggingSettings" + "title": "ListAttributeParametersWrite" }, - "MainSettings": { + "ListAttributeRead": { "properties": { - "docs_index_path": { - "type": "string", - "format": "path", - "title": "Docs Index Path", - "description": "Full path of saved json containing pre-indexed documentation", - "default": "/opt/infrahub/docs/build/search-index.json" - }, - "internal_address": { + "id": { "anyOf": [ { "type": "string" @@ -5355,286 +5465,129 @@ "type": "null" } ], - "title": "Internal Address" - }, - "allow_anonymous_access": { - "type": "boolean", - "title": "Allow Anonymous Access", - "description": "Indicates if the system allows anonymous read access", - "default": true + "title": "Id", + "description": "The ID of the attribute" }, - "anonymous_access_role": { + "name": { "type": "string", - "title": "Anonymous Access Role", - "description": "Name of the role defining which permissions anonymous users have", - "default": "Anonymous User" - }, - "telemetry_optout": { - "type": "boolean", - "title": "Telemetry Optout", - "description": "Disable anonymous usage reporting", - "default": false + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." }, - "telemetry_endpoint": { + "kind": { "type": "string", - "title": "Telemetry Endpoint", - "default": "https://telemetry.opsmill.cloud/infrahub" + "const": "List", + "title": "Kind", + "description": "Defines the type of the attribute." }, - "permission_backends": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Permission Backends", - "description": "List of modules to handle permissions, they will be run in the given order", - "default": [ - "infrahub.permissions.LocalPermissionBackend" - ] + "enum": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Enum", + "description": "Define a list of valid values for the attribute." }, - "public_url": { + "computed_attribute": { "anyOf": [ { - "type": "string" + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserRead" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Read" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonRead" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Read", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonRead", + "User": "#/components/schemas/ComputedAttributeUserRead" + } + } }, { "type": "null" } ], - "title": "Public Url", - "description": "Define the public URL of the Infrahub, might be required for OAuth2 and OIDC depending on your infrastructure." + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." }, - "schema_strict_mode": { - "type": "boolean", - "title": "Schema Strict Mode", - "description": "Enable strict schema validation. When set to `False`, `human_friendly_id` schema fields should not necessarily target a unique combination of peer attributes.", - "default": true + "choices": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/DropdownChoiceRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." }, - "diff_update_after_merge": { - "type": "boolean", - "title": "Diff Update After Merge", - "description": "When enabled, diff updates are triggered for active branches after a branch merge.", - "default": true + "regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Regex", + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" }, - "delete_branch_after_merge": { - "type": "boolean", - "title": "Delete Branch After Merge", - "description": "When enabled, the Infrahub branch is automatically deleted after a successful merge.", - "default": false - }, - "merge_failure_grace_period_seconds": { - "type": "integer", - "minimum": 0.0, - "title": "Merge Failure Grace Period Seconds", - "description": "How long a branch may stay in MERGING with a dead merge-lock holder before it is flagged MERGE_FAILED.", - "default": 180 - } - }, - "additionalProperties": false, - "type": "object", - "title": "MainSettings" - }, - "Menu": { - "properties": { - "sections": { - "additionalProperties": { - "items": { - "$ref": "#/components/schemas/MenuItemList" - }, - "type": "array" - }, - "type": "object", - "title": "Sections" - } - }, - "type": "object", - "title": "Menu" - }, - "MenuItemList": { - "properties": { - "id": { + "max_length": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Id" - }, - "namespace": { - "type": "string", - "title": "Namespace", - "description": "Namespace of the menu item" - }, - "name": { - "type": "string", - "title": "Name", - "description": "Name of the menu item" - }, - "description": { - "type": "string", - "title": "Description", - "description": "Description of the menu item", - "default": "" - }, - "protected": { - "type": "boolean", - "title": "Protected", - "description": "Whether the menu item is protected", - "default": false - }, - "label": { - "type": "string", - "title": "Label", - "description": "Title of the menu item" - }, - "path": { - "type": "string", - "title": "Path", - "description": "URL endpoint if applicable", - "default": "" - }, - "icon": { - "type": "string", - "title": "Icon", - "description": "The icon to show for the current view", - "default": "" - }, - "kind": { - "type": "string", - "title": "Kind", - "description": "Kind of the model associated with this menuitem if applicable", - "default": "" - }, - "order_weight": { - "type": "integer", - "title": "Order Weight", - "default": 5000 - }, - "section": { - "$ref": "#/components/schemas/MenuSection", - "default": "object" - }, - "permissions": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Permissions" - }, - "children": { - "items": { - "$ref": "#/components/schemas/MenuItemList" - }, - "type": "array", - "title": "Children", - "description": "Child objects" + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" }, - "identifier": { - "type": "string", - "title": "Identifier", - "readOnly": true - } - }, - "type": "object", - "required": [ - "namespace", - "name", - "label", - "identifier" - ], - "title": "MenuItemList" - }, - "MenuSection": { - "type": "string", - "enum": [ - "object", - "internal" - ], - "title": "MenuSection" - }, - "NodeExtensionSchema": { - "properties": { - "id": { + "min_length": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Id" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "default": "present" - }, - "kind": { - "type": "string", - "title": "Kind" - }, - "attributes": { - "items": { - "$ref": "#/components/schemas/AttributeSchema-Input" - }, - "type": "array", - "title": "Attributes" + "title": "Min Length", + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" }, - "relationships": { - "items": { - "$ref": "#/components/schemas/RelationshipSchema" - }, - "type": "array", - "title": "Relationships" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "kind" - ], - "title": "NodeExtensionSchema" - }, - "NodeSchema": { - "properties": { - "id": { + "label": { "anyOf": [ { - "type": "string" + "type": "string", + "maxLength": 64 }, { "type": "null" } ], - "title": "Id", - "description": "The ID of the node", - "update": "not_applicable" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "description": "Expected state of the node/generic after loading the schema", - "default": "present", - "update": "not_applicable" - }, - "name": { - "type": "string", - "maxLength": 32, - "minLength": 2, - "pattern": "^[A-Z][a-zA-Z0-9]+$", - "title": "Name", - "description": "Node name, must be unique within a namespace and must start with an uppercase letter.", - "update": "migration_required" - }, - "namespace": { - "type": "string", - "maxLength": 64, - "minLength": 3, - "pattern": "^[A-Z][a-z0-9]+$", - "title": "Namespace", - "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions.", - "update": "migration_required" + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" }, "description": { "anyOf": [ @@ -5647,103 +5600,121 @@ } ], "title": "Description", - "description": "Short description of the model, will be visible in the frontend.", - "update": "allowed" + "description": "Short description of the attribute." }, - "label": { - "anyOf": [ - { - "type": "string", - "maxLength": 64 - }, - { - "type": "null" - } - ], - "title": "Label", - "description": "Human friendly representation of the name/kind", - "update": "allowed" + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", + "default": false }, - "branch": { - "$ref": "#/components/schemas/BranchSupportType", - "description": "Type of branch support for the model.", - "default": "aware", - "update": "not_supported" + "unique": { + "type": "boolean", + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false }, - "default_filter": { + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { "anyOf": [ { - "type": "string", - "pattern": "^[a-z0-9\\_]*$" + "$ref": "#/components/schemas/BranchSupportType" }, { "type": "null" } ], - "title": "Default Filter", - "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)", - "update": "allowed" + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." }, - "human_friendly_id": { + "order_weight": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "integer" }, { "type": "null" } ], - "title": "Human Friendly Id", - "description": "Human friendly and unique identifier for the object.", - "update": "allowed" + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." }, - "display_label": { + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true + }, + "default_value": { "anyOf": [ - { - "type": "string" - }, + {}, { "type": "null" } ], - "title": "Display Label", - "description": "Attribute or Jinja2 template to use to generate the display label", - "update": "allowed" + "title": "Default Value", + "description": "Default value of the attribute." }, - "display_labels": { - "anyOf": [ - { - "items": { - "type": "string", - "deprecationMessage": "display_labels are deprecated use display_label instead" - }, - "type": "array" + "inherited": { + "type": "boolean", + "title": "Inherited", + "description": "Internal value to indicate if the attribute was inherited from a Generic node.", + "default": false + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 }, { "type": "null" } ], - "title": "Display Labels", - "description": "List of attributes to use to generate the display label (deprecated)", - "update": "allowed" + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" }, - "include_in_menu": { + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + }, + "parameters": { "anyOf": [ { - "type": "boolean" + "$ref": "#/components/schemas/ListAttributeParametersRead" }, { "type": "null" } ], - "title": "Include In Menu", - "description": "Defines if objects of this kind should be included in the menu.", - "update": "allowed" - }, - "menu_placement": { + "description": "Extra parameters specific to this kind of attribute" + } + }, + "type": "object", + "required": [ + "name", + "kind" + ], + "title": "ListAttributeRead" + }, + "ListAttributeWrite": { + "properties": { + "id": { "anyOf": [ { "type": "string" @@ -5752,47 +5723,71 @@ "type": "null" } ], - "title": "Menu Placement", - "description": "Defines where in the menu this object should be placed.", - "update": "allowed" + "title": "Id", + "description": "The ID of the attribute" }, - "icon": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." + }, + "kind": { + "type": "string", + "const": "List", + "title": "Kind", + "description": "Defines the type of the attribute." + }, + "enum": { "anyOf": [ { - "type": "string" + "items": {}, + "type": "array" }, { "type": "null" } ], - "title": "Icon", - "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/", - "update": "allowed" + "title": "Enum", + "description": "Define a list of valid values for the attribute." }, - "order_by": { + "computed_attribute": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserWrite" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Write" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonWrite" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Write", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonWrite", + "User": "#/components/schemas/ComputedAttributeUserWrite" + } + } }, { "type": "null" } ], - "title": "Order By", - "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc.", - "update": "allowed" + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." }, - "uniqueness_constraints": { + "choices": { "anyOf": [ { "items": { - "items": { - "type": "string" - }, - "type": "array" + "$ref": "#/components/schemas/DropdownChoiceWrite" }, "type": "array" }, @@ -5800,11 +5795,10 @@ "type": "null" } ], - "title": "Uniqueness Constraints", - "description": "List of multi-element uniqueness constraints that can combine relationships and attributes", - "update": "migration_required" + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." }, - "documentation": { + "regex": { "anyOf": [ { "type": "string" @@ -5813,130 +5807,89 @@ "type": "null" } ], - "title": "Documentation", - "description": "Link to a documentation associated with this object, can be internal or external.", - "update": "allowed" - }, - "attributes": { - "items": { - "$ref": "#/components/schemas/AttributeSchema-Input" - }, - "type": "array", - "title": "Attributes", - "description": "Node attributes", - "update": "not_applicable" - }, - "relationships": { - "items": { - "$ref": "#/components/schemas/RelationshipSchema" - }, - "type": "array", - "title": "Relationships", - "description": "Node Relationships", - "update": "not_applicable" - }, - "inherit_from": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Inherit From", - "description": "List of Generic Kind that this node is inheriting from", - "update": "validate_constraint" - }, - "generate_profile": { - "type": "boolean", - "title": "Generate Profile", - "description": "Indicate if a profile schema should be generated for this schema", - "default": true, - "update": "validate_constraint" - }, - "generate_template": { - "type": "boolean", - "title": "Generate Template", - "description": "Indicate if an object template schema should be generated for this schema", - "default": false, - "update": "allowed" + "title": "Regex", + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" }, - "hierarchy": { + "max_length": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Hierarchy", - "description": "Internal value to track the name of the Hierarchy, must match the name of a Generic supporting hierarchical mode", - "update": "validate_constraint" + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" }, - "parent": { + "min_length": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Parent", - "description": "Expected Kind for the parent node in a Hierarchy, default to the main generic defined if not defined.", - "update": "validate_constraint" + "title": "Min Length", + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" }, - "children": { + "label": { "anyOf": [ { - "type": "string" + "type": "string", + "maxLength": 64 }, { "type": "null" } ], - "title": "Children", - "description": "Expected Kind for the children nodes in a Hierarchy, default to the main generic defined if not defined.", - "update": "validate_constraint" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "name", - "namespace" - ], - "title": "NodeSchema" - }, - "NumberAttributeParameters": { - "properties": { - "id": { + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" + }, + "description": { "anyOf": [ { - "type": "string" + "type": "string", + "maxLength": 128 }, { "type": "null" } ], - "title": "Id" + "title": "Description", + "description": "Short description of the attribute." }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "default": "present" + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", + "default": false }, - "min_value": { + "unique": { + "type": "boolean", + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { "anyOf": [ { - "type": "integer" + "$ref": "#/components/schemas/BranchSupportType" }, { "type": "null" } ], - "title": "Min Value", - "description": "Set a minimum value allowed.", - "update": "validate_constraint" + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." }, - "max_value": { + "order_weight": { "anyOf": [ { "type": "integer" @@ -5945,169 +5898,199 @@ "type": "null" } ], - "title": "Max Value", - "description": "Set a maximum value allowed.", - "update": "validate_constraint" + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." }, - "excluded_values": { + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true + }, + "default_value": { "anyOf": [ - { - "type": "string", - "pattern": "^(\\d+(?:-\\d+)?)(?:,\\d+(?:-\\d+)?)*$" - }, + {}, { "type": "null" } ], - "title": "Excluded Values", - "description": "List of values or range of values not allowed for the attribute, format is: '100,150-200,280,300-400'", - "update": "validate_constraint" - } - }, - "additionalProperties": false, - "type": "object", - "title": "NumberAttributeParameters" - }, - "NumberPoolParameters": { - "properties": { - "id": { + "title": "Default Value", + "description": "Default value of the attribute." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" + }, + "deprecation": { "anyOf": [ { - "type": "string" + "type": "string", + "maxLength": 128 }, { "type": "null" } ], - "title": "Id" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "default": "present" - }, - "end_range": { - "type": "integer", - "title": "End Range", - "description": "End range for numbers for the associated NumberPool", - "default": 9223372036854775807, - "update": "validate_constraint" + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" }, - "start_range": { - "type": "integer", - "title": "Start Range", - "description": "Start range for numbers for the associated NumberPool", - "default": 1, - "update": "validate_constraint" + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" }, - "number_pool_id": { + "parameters": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/ListAttributeParametersWrite" }, { "type": "null" } ], - "title": "Number Pool Id", - "description": "The ID of the numberpool associated with this attribute. Only set after the number pool has been provisioned.", - "update": "not_supported" + "description": "Extra parameters specific to this kind of attribute" } }, "additionalProperties": false, "type": "object", - "title": "NumberPoolParameters" + "required": [ + "name", + "kind" + ], + "title": "ListAttributeWrite" }, - "PasswordCredential": { + "LoggingSettings": { "properties": { - "username": { - "type": "string", - "title": "Username", - "description": "Name of the user that is logging in." - }, - "password": { - "type": "string", - "title": "Password", - "description": "The password of the user." + "remote": { + "$ref": "#/components/schemas/RemoteLoggingSettings", + "default": { + "enable": false + } } }, + "additionalProperties": false, "type": "object", - "required": [ - "username", - "password" - ], - "title": "PasswordCredential" + "title": "LoggingSettings" }, - "PolicySettings": { + "MainSettings": { "properties": { - "required_proposed_change_approvals": { - "type": "integer", - "minimum": 0.0, - "title": "Required Proposed Change Approvals", - "description": "Number of approvals required for proposed changes. (Enterprise only: not available in the community version.)", - "default": 0 + "docs_index_path": { + "type": "string", + "format": "path", + "title": "Docs Index Path", + "description": "Full path of saved json containing pre-indexed documentation", + "default": "/opt/infrahub/docs/build/search-index.json" }, - "revoke_proposed_change_approvals": { + "internal_address": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Internal Address" + }, + "allow_anonymous_access": { "type": "boolean", - "title": "Revoke Proposed Change Approvals", - "description": "Boolean indicating whether performing changes on a proposed change branch should revoke existing approvals. (Enterprise only: not available in the community version.)", + "title": "Allow Anonymous Access", + "description": "Indicates if the system allows anonymous read access", + "default": true + }, + "anonymous_access_role": { + "type": "string", + "title": "Anonymous Access Role", + "description": "Name of the role defining which permissions anonymous users have", + "default": "Anonymous User" + }, + "telemetry_optout": { + "type": "boolean", + "title": "Telemetry Optout", + "description": "Disable anonymous usage reporting", + "default": false + }, + "telemetry_endpoint": { + "type": "string", + "title": "Telemetry Endpoint", + "default": "https://telemetry.opsmill.cloud/infrahub" + }, + "permission_backends": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Permission Backends", + "description": "List of modules to handle permissions, they will be run in the given order", + "default": [ + "infrahub.permissions.LocalPermissionBackend" + ] + }, + "public_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Public Url", + "description": "Define the public URL of the Infrahub, might be required for OAuth2 and OIDC depending on your infrastructure." + }, + "schema_strict_mode": { + "type": "boolean", + "title": "Schema Strict Mode", + "description": "Enable strict schema validation. When set to `False`, `human_friendly_id` schema fields should not necessarily target a unique combination of peer attributes.", + "default": true + }, + "diff_update_after_merge": { + "type": "boolean", + "title": "Diff Update After Merge", + "description": "When enabled, diff updates are triggered for active branches after a branch merge.", + "default": true + }, + "delete_branch_after_merge": { + "type": "boolean", + "title": "Delete Branch After Merge", + "description": "When enabled, the Infrahub branch is automatically deleted after a successful merge.", "default": false + }, + "merge_failure_grace_period_seconds": { + "type": "integer", + "minimum": 0.0, + "title": "Merge Failure Grace Period Seconds", + "description": "How long a branch may stay in MERGING with a dead merge-lock holder before it is flagged MERGE_FAILED.", + "default": 180 } }, "additionalProperties": false, "type": "object", - "title": "PolicySettings" + "title": "MainSettings" }, - "QueryPayload": { + "Menu": { "properties": { - "variables": { - "additionalProperties": true, + "sections": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/MenuItemList" + }, + "type": "array" + }, "type": "object", - "title": "Variables" + "title": "Sections" } }, "type": "object", - "title": "QueryPayload" - }, - "RelationshipCardinality": { - "type": "string", - "enum": [ - "one", - "many" - ], - "title": "RelationshipCardinality" - }, - "RelationshipDeleteBehavior": { - "type": "string", - "enum": [ - "no-action", - "cascade" - ], - "title": "RelationshipDeleteBehavior" - }, - "RelationshipDirection": { - "type": "string", - "enum": [ - "bidirectional", - "outbound", - "inbound" - ], - "title": "RelationshipDirection" - }, - "RelationshipKind": { - "type": "string", - "enum": [ - "Generic", - "Attribute", - "Component", - "Parent", - "Group", - "Hierarchy", - "Profile", - "Template" - ], - "title": "RelationshipKind" + "title": "Menu" }, - "RelationshipSchema": { + "MenuItemList": { "properties": { "id": { "anyOf": [ @@ -6118,176 +6101,264 @@ "type": "null" } ], - "title": "Id", - "description": "The ID of the relationship schema", - "update": "not_applicable" + "title": "Id" }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "description": "Expected state of the relationship after loading the schema", - "default": "present", - "update": "not_applicable" + "namespace": { + "type": "string", + "title": "Namespace", + "description": "Namespace of the menu item" }, "name": { "type": "string", - "maxLength": 64, - "minLength": 3, - "pattern": "^[a-z0-9\\_]+$", "title": "Name", - "description": "Relationship name, must be unique within a model and must be all lowercase.", - "update": "allowed" + "description": "Name of the menu item" }, - "peer": { + "description": { "type": "string", - "pattern": "^[A-Z][a-zA-Z0-9]+$", - "title": "Peer", - "description": "Type (kind) of objects supported on the other end of the relationship.", - "update": "validate_constraint" + "title": "Description", + "description": "Description of the menu item", + "default": "" }, - "kind": { - "$ref": "#/components/schemas/RelationshipKind", - "description": "Defines the type of the relationship.", - "default": "Generic", - "update": "allowed" + "protected": { + "type": "boolean", + "title": "Protected", + "description": "Whether the menu item is protected", + "default": false }, "label": { - "anyOf": [ - { - "type": "string", - "maxLength": 64 - }, - { - "type": "null" - } - ], + "type": "string", "title": "Label", - "description": "Human friendly representation of the name. Will be autogenerated if not provided", - "update": "allowed" + "description": "Title of the menu item" }, - "description": { - "anyOf": [ - { - "type": "string", - "maxLength": 128 - }, - { - "type": "null" - } - ], - "title": "Description", - "description": "Short description of the relationship.", - "update": "allowed" + "path": { + "type": "string", + "title": "Path", + "description": "URL endpoint if applicable", + "default": "" }, - "identifier": { - "anyOf": [ - { - "type": "string", - "maxLength": 128, - "pattern": "^[a-z0-9\\_]+$" - }, - { - "type": "null" - } - ], - "title": "Identifier", - "description": "Unique identifier of the relationship within a model, identifiers must match to traverse a relationship on both direction.", - "update": "not_supported" + "icon": { + "type": "string", + "title": "Icon", + "description": "The icon to show for the current view", + "default": "" }, - "cardinality": { - "$ref": "#/components/schemas/RelationshipCardinality", - "description": "Defines how many objects are expected on the other side of the relationship.", - "default": "many", - "update": "validate_constraint" + "kind": { + "type": "string", + "title": "Kind", + "description": "Kind of the model associated with this menuitem if applicable", + "default": "" }, - "min_count": { + "order_weight": { "type": "integer", - "title": "Min Count", - "description": "Defines the minimum objects allowed on the other side of the relationship.", - "default": 0, - "update": "validate_constraint" + "title": "Order Weight", + "default": 5000 }, - "max_count": { - "type": "integer", - "title": "Max Count", - "description": "Defines the maximum objects allowed on the other side of the relationship.", - "default": 0, - "update": "validate_constraint" + "section": { + "$ref": "#/components/schemas/MenuSection", + "default": "object" }, - "common_parent": { - "anyOf": [ - { - "type": "string" + "permissions": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Permissions" + }, + "children": { + "items": { + "$ref": "#/components/schemas/MenuItemList" + }, + "type": "array", + "title": "Children", + "description": "Child objects" + }, + "identifier": { + "type": "string", + "title": "Identifier", + "readOnly": true + } + }, + "type": "object", + "required": [ + "namespace", + "name", + "label", + "identifier" + ], + "title": "MenuItemList" + }, + "MenuSection": { + "type": "string", + "enum": [ + "object", + "internal" + ], + "title": "MenuSection" + }, + "NodeExtensionWrite": { + "properties": { + "kind": { + "type": "string", + "title": "Kind", + "description": "Kind of the existing node to extend." + }, + "attributes": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/TextAttributeWrite" + }, + { + "$ref": "#/components/schemas/NumberAttributeWrite" + }, + { + "$ref": "#/components/schemas/ListAttributeWrite" + }, + { + "$ref": "#/components/schemas/NumberPoolAttributeWrite" + }, + { + "$ref": "#/components/schemas/GenericAttributeWrite" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Any": "#/components/schemas/GenericAttributeWrite", + "Bandwidth": "#/components/schemas/GenericAttributeWrite", + "Boolean": "#/components/schemas/GenericAttributeWrite", + "Checkbox": "#/components/schemas/GenericAttributeWrite", + "Color": "#/components/schemas/GenericAttributeWrite", + "DateTime": "#/components/schemas/GenericAttributeWrite", + "Dropdown": "#/components/schemas/GenericAttributeWrite", + "Email": "#/components/schemas/GenericAttributeWrite", + "File": "#/components/schemas/GenericAttributeWrite", + "HashedPassword": "#/components/schemas/GenericAttributeWrite", + "ID": "#/components/schemas/GenericAttributeWrite", + "IPHost": "#/components/schemas/GenericAttributeWrite", + "IPNetwork": "#/components/schemas/GenericAttributeWrite", + "JSON": "#/components/schemas/GenericAttributeWrite", + "List": "#/components/schemas/ListAttributeWrite", + "MacAddress": "#/components/schemas/GenericAttributeWrite", + "Number": "#/components/schemas/NumberAttributeWrite", + "NumberPool": "#/components/schemas/NumberPoolAttributeWrite", + "Password": "#/components/schemas/GenericAttributeWrite", + "Text": "#/components/schemas/TextAttributeWrite", + "TextArea": "#/components/schemas/TextAttributeWrite", + "URL": "#/components/schemas/GenericAttributeWrite" + } + } + }, + "type": "array", + "title": "Attributes", + "description": "Attributes to add to the existing node." + }, + "relationships": { + "items": { + "$ref": "#/components/schemas/RelationshipSchemaWrite" + }, + "type": "array", + "title": "Relationships", + "description": "Relationships to add to the existing node." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "kind" + ], + "title": "NodeExtensionWrite" + }, + "NodeSchemaWrite": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" }, { "type": "null" } ], - "title": "Common Parent", - "description": "Name of a parent relationship on the peer schema that must share the same related object with the object's parent.", - "update": "validate_constraint" + "title": "Id", + "description": "The ID of the node" }, - "common_relatives": { + "name": { + "type": "string", + "maxLength": 32, + "minLength": 2, + "pattern": "^[A-Z][a-zA-Z0-9]+$", + "title": "Name", + "description": "Node name, must be unique within a namespace and must start with an uppercase letter." + }, + "namespace": { + "type": "string", + "maxLength": 64, + "minLength": 3, + "pattern": "^[A-Z][a-z0-9]+$", + "title": "Namespace", + "description": "Node Namespace, Namespaces are used to organize models into logical groups and to prevent name collisions." + }, + "description": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "string", + "maxLength": 128 }, { "type": "null" } ], - "title": "Common Relatives", - "description": "List of relationship names on the peer schema for which all objects must share the same set of peers.", - "update": "allowed" + "title": "Description", + "description": "Short description of the model, will be visible in the frontend." }, - "order_weight": { + "label": { "anyOf": [ { - "type": "integer" + "type": "string", + "maxLength": 64 }, { "type": "null" } ], - "title": "Order Weight", - "description": "Number used to order the relationship in the frontend (table and view). Lowest value will be ordered first.", - "update": "allowed" - }, - "optional": { - "type": "boolean", - "title": "Optional", - "description": "Indicate if this relationship is mandatory or optional.", - "default": true, - "update": "validate_constraint" + "title": "Label", + "description": "Human friendly representation of the name/kind" }, "branch": { + "$ref": "#/components/schemas/BranchSupportType", + "description": "Type of branch support for the model.", + "default": "aware" + }, + "default_filter": { "anyOf": [ { - "$ref": "#/components/schemas/BranchSupportType" + "type": "string", + "pattern": "^[a-z0-9\\_]*$" }, { "type": "null" } ], - "description": "Type of branch support for the relationship. If not defined, it will be determined based on both peers.", - "update": "not_supported" - }, - "inherited": { - "type": "boolean", - "title": "Inherited", - "description": "Internal value to indicate if the relationship was inherited from a Generic node.", - "default": false, - "update": "not_applicable" + "title": "Default Filter", + "description": "Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)" }, - "direction": { - "$ref": "#/components/schemas/RelationshipDirection", - "description": "Defines the direction of the relationship, Unidirectional relationship are required when the same model is on both side.", - "default": "bidirectional", - "update": "not_supported" + "human_friendly_id": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Human Friendly Id", + "description": "Human friendly and unique identifier for the object." }, - "hierarchical": { + "display_label": { "anyOf": [ { "type": "string" @@ -6296,72 +6367,37 @@ "type": "null" } ], - "title": "Hierarchical", - "description": "Internal attribute to track the type of hierarchy this relationship is part of, must match a valid Generic Kind", - "update": "not_supported" + "title": "Display Label", + "description": "Attribute or Jinja2 template to use to generate the display label" }, - "on_delete": { + "display_labels": { "anyOf": [ { - "$ref": "#/components/schemas/RelationshipDeleteBehavior" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "description": "Default is no-action. If cascade, related node(s) are deleted when this node is deleted.", - "update": "allowed" - }, - "allow_override": { - "$ref": "#/components/schemas/AllowOverrideType", - "description": "Type of allowed override for the relationship.", - "default": "any", - "update": "allowed" - }, - "read_only": { - "type": "boolean", - "title": "Read Only", - "description": "Set the relationship as read-only, users won't be able to change its value.", - "default": false, - "update": "allowed" + "title": "Display Labels", + "description": "List of attributes to use to generate the display label (deprecated)" }, - "deprecation": { + "include_in_menu": { "anyOf": [ { - "type": "string", - "maxLength": 128 + "type": "boolean" }, { "type": "null" } ], - "title": "Deprecation", - "description": "Mark relationship as deprecated and provide a user-friendly message to display", - "update": "allowed" - }, - "display": { - "$ref": "#/components/schemas/SchemaAttributeDisplay", - "description": "Controls where the relationship is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", - "default": "default", - "update": "allowed" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "name", - "peer" - ], - "title": "RelationshipSchema" - }, - "RemoteLoggingSettings": { - "properties": { - "enable": { - "type": "boolean", - "title": "Enable", - "default": false + "title": "Include In Menu", + "description": "Defines if objects of this kind should be included in the menu." }, - "frontend_dsn": { + "menu_placement": { "anyOf": [ { "type": "string" @@ -6370,9 +6406,10 @@ "type": "null" } ], - "title": "Frontend Dsn" + "title": "Menu Placement", + "description": "Defines where in the menu this object should be placed." }, - "api_server_dsn": { + "icon": { "anyOf": [ { "type": "string" @@ -6381,166 +6418,259 @@ "type": "null" } ], - "title": "Api Server Dsn" + "title": "Icon", + "description": "Defines the icon to use in the menu. Must be a valid value from the MDI library https://icon-sets.iconify.design/mdi/" }, - "git_agent_dsn": { + "order_by": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "title": "Git Agent Dsn" - } - }, - "additionalProperties": false, - "type": "object", - "title": "RemoteLoggingSettings" - }, - "RemoteSendStatus": { - "type": "string", - "enum": [ - "pending", - "sent", - "skipped", - "failed" - ], - "title": "RemoteSendStatus" - }, - "SSOInfo": { - "properties": { - "providers": { - "items": { - "$ref": "#/components/schemas/SSOProviderInfo" - }, - "type": "array", - "title": "Providers" + "title": "Order By", + "description": "List of entries to order results by. Supports attributes, relationship attributes, and node_metadata with __asc/__desc." }, - "enabled": { - "type": "boolean", - "title": "Enabled", - "readOnly": true - } - }, - "type": "object", - "required": [ - "enabled" - ], - "title": "SSOInfo" - }, - "SSOProtocol": { - "type": "string", - "enum": [ - "oauth2", - "oidc" - ], - "title": "SSOProtocol" - }, - "SSOProviderInfo": { - "properties": { - "name": { - "type": "string", - "title": "Name" + "uniqueness_constraints": { + "anyOf": [ + { + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Uniqueness Constraints", + "description": "List of multi-element uniqueness constraints that can combine relationships and attributes" }, - "display_label": { - "type": "string", - "title": "Display Label" + "documentation": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Documentation", + "description": "Link to a documentation associated with this object, can be internal or external." }, - "icon": { - "type": "string", - "title": "Icon" + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the node/generic after loading the schema", + "default": "present" }, - "protocol": { - "$ref": "#/components/schemas/SSOProtocol" + "attributes": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/TextAttributeWrite" + }, + { + "$ref": "#/components/schemas/NumberAttributeWrite" + }, + { + "$ref": "#/components/schemas/ListAttributeWrite" + }, + { + "$ref": "#/components/schemas/NumberPoolAttributeWrite" + }, + { + "$ref": "#/components/schemas/GenericAttributeWrite" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Any": "#/components/schemas/GenericAttributeWrite", + "Bandwidth": "#/components/schemas/GenericAttributeWrite", + "Boolean": "#/components/schemas/GenericAttributeWrite", + "Checkbox": "#/components/schemas/GenericAttributeWrite", + "Color": "#/components/schemas/GenericAttributeWrite", + "DateTime": "#/components/schemas/GenericAttributeWrite", + "Dropdown": "#/components/schemas/GenericAttributeWrite", + "Email": "#/components/schemas/GenericAttributeWrite", + "File": "#/components/schemas/GenericAttributeWrite", + "HashedPassword": "#/components/schemas/GenericAttributeWrite", + "ID": "#/components/schemas/GenericAttributeWrite", + "IPHost": "#/components/schemas/GenericAttributeWrite", + "IPNetwork": "#/components/schemas/GenericAttributeWrite", + "JSON": "#/components/schemas/GenericAttributeWrite", + "List": "#/components/schemas/ListAttributeWrite", + "MacAddress": "#/components/schemas/GenericAttributeWrite", + "Number": "#/components/schemas/NumberAttributeWrite", + "NumberPool": "#/components/schemas/NumberPoolAttributeWrite", + "Password": "#/components/schemas/GenericAttributeWrite", + "Text": "#/components/schemas/TextAttributeWrite", + "TextArea": "#/components/schemas/TextAttributeWrite", + "URL": "#/components/schemas/GenericAttributeWrite" + } + } + }, + "type": "array", + "title": "Attributes", + "description": "Node attributes" }, - "authorize_path": { - "type": "string", - "title": "Authorize Path", - "readOnly": true + "relationships": { + "items": { + "$ref": "#/components/schemas/RelationshipSchemaWrite" + }, + "type": "array", + "title": "Relationships", + "description": "Node Relationships" }, - "token_path": { - "type": "string", - "title": "Token Path", - "readOnly": true + "inherit_from": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Inherit From", + "description": "List of Generic Kind that this node is inheriting from" + }, + "generate_profile": { + "type": "boolean", + "title": "Generate Profile", + "description": "Indicate if a profile schema should be generated for this schema", + "default": true + }, + "generate_template": { + "type": "boolean", + "title": "Generate Template", + "description": "Indicate if an object template schema should be generated for this schema", + "default": false + }, + "parent": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Parent", + "description": "Expected Kind for the parent node in a Hierarchy, default to the main generic defined if not defined." + }, + "children": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Children", + "description": "Expected Kind for the children nodes in a Hierarchy, default to the main generic defined if not defined." } }, + "additionalProperties": false, "type": "object", "required": [ "name", - "display_label", - "icon", - "protocol", - "authorize_path", - "token_path" - ], - "title": "SSOProviderInfo" - }, - "SchemaAttributeDisplay": { - "type": "string", - "enum": [ - "default", - "extra" + "namespace" ], - "title": "SchemaAttributeDisplay" + "title": "NodeSchemaWrite" }, - "SchemaBranchHash": { + "NumberAttributeParametersRead": { "properties": { - "main": { - "type": "string", - "title": "Main" + "min_value": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Min Value", + "description": "Set a minimum value allowed." }, - "nodes": { - "additionalProperties": { - "type": "string" - }, - "type": "object", - "title": "Nodes" + "max_value": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Value", + "description": "Set a maximum value allowed." }, - "generics": { - "additionalProperties": { - "type": "string" - }, - "type": "object", - "title": "Generics" + "excluded_values": { + "anyOf": [ + { + "type": "string", + "pattern": "^(\\d+(?:-\\d+)?)(?:,\\d+(?:-\\d+)?)*$" + }, + { + "type": "null" + } + ], + "title": "Excluded Values", + "description": "List of values or range of values not allowed for the attribute, format is: '100,150-200,280,300-400'" } }, "type": "object", - "required": [ - "main" - ], - "title": "SchemaBranchHash" + "title": "NumberAttributeParametersRead" }, - "SchemaDiff": { + "NumberAttributeParametersWrite": { "properties": { - "added": { - "additionalProperties": { - "$ref": "#/components/schemas/HashableModelDiff" - }, - "type": "object", - "title": "Added" + "min_value": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Min Value", + "description": "Set a minimum value allowed." }, - "changed": { - "additionalProperties": { - "$ref": "#/components/schemas/HashableModelDiff" - }, - "type": "object", - "title": "Changed" + "max_value": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Value", + "description": "Set a maximum value allowed." }, - "removed": { - "additionalProperties": { - "$ref": "#/components/schemas/HashableModelDiff" - }, - "type": "object", - "title": "Removed" + "excluded_values": { + "anyOf": [ + { + "type": "string", + "pattern": "^(\\d+(?:-\\d+)?)(?:,\\d+(?:-\\d+)?)*$" + }, + { + "type": "null" + } + ], + "title": "Excluded Values", + "description": "List of values or range of values not allowed for the attribute, format is: '100,150-200,280,300-400'" } }, "additionalProperties": false, "type": "object", - "title": "SchemaDiff" + "title": "NumberAttributeParametersWrite" }, - "SchemaExtension": { + "NumberAttributeRead": { "properties": { "id": { "anyOf": [ @@ -6551,50 +6681,1825 @@ "type": "null" } ], - "title": "Id" - }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "default": "present" + "title": "Id", + "description": "The ID of the attribute" }, - "nodes": { - "items": { - "$ref": "#/components/schemas/NodeExtensionSchema" - }, - "type": "array", - "title": "Nodes" - } - }, - "additionalProperties": false, - "type": "object", - "title": "SchemaExtension" - }, - "SchemaLoadAPI": { - "properties": { - "version": { + "name": { "type": "string", - "title": "Version" + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." + }, + "kind": { + "type": "string", + "const": "Number", + "title": "Kind", + "description": "Defines the type of the attribute." + }, + "enum": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Enum", + "description": "Define a list of valid values for the attribute." + }, + "computed_attribute": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserRead" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Read" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonRead" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Read", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonRead", + "User": "#/components/schemas/ComputedAttributeUserRead" + } + } + }, + { + "type": "null" + } + ], + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." + }, + "choices": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/DropdownChoiceRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." + }, + "regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Regex", + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" + }, + "max_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" + }, + "min_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Min Length", + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" + }, + "label": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" + } + ], + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" + }, + "description": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Short description of the attribute." + }, + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", + "default": false + }, + "unique": { + "type": "boolean", + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { + "anyOf": [ + { + "$ref": "#/components/schemas/BranchSupportType" + }, + { + "type": "null" + } + ], + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." + }, + "order_weight": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." + }, + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true + }, + "default_value": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Default Value", + "description": "Default value of the attribute." + }, + "inherited": { + "type": "boolean", + "title": "Inherited", + "description": "Internal value to indicate if the attribute was inherited from a Generic node.", + "default": false + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" + }, + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + }, + "parameters": { + "anyOf": [ + { + "$ref": "#/components/schemas/NumberAttributeParametersRead" + }, + { + "type": "null" + } + ], + "description": "Extra parameters specific to this kind of attribute" + } + }, + "type": "object", + "required": [ + "name", + "kind" + ], + "title": "NumberAttributeRead" + }, + "NumberAttributeWrite": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id", + "description": "The ID of the attribute" + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." + }, + "kind": { + "type": "string", + "const": "Number", + "title": "Kind", + "description": "Defines the type of the attribute." + }, + "enum": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Enum", + "description": "Define a list of valid values for the attribute." + }, + "computed_attribute": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserWrite" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Write" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonWrite" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Write", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonWrite", + "User": "#/components/schemas/ComputedAttributeUserWrite" + } + } + }, + { + "type": "null" + } + ], + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." + }, + "choices": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/DropdownChoiceWrite" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." + }, + "regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Regex", + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" + }, + "max_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" + }, + "min_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Min Length", + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" + }, + "label": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" + } + ], + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" + }, + "description": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Short description of the attribute." + }, + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", + "default": false + }, + "unique": { + "type": "boolean", + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { + "anyOf": [ + { + "$ref": "#/components/schemas/BranchSupportType" + }, + { + "type": "null" + } + ], + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." + }, + "order_weight": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." + }, + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true + }, + "default_value": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Default Value", + "description": "Default value of the attribute." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" + }, + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + }, + "parameters": { + "anyOf": [ + { + "$ref": "#/components/schemas/NumberAttributeParametersWrite" + }, + { + "type": "null" + } + ], + "description": "Extra parameters specific to this kind of attribute" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "kind" + ], + "title": "NumberAttributeWrite" + }, + "NumberPoolAttributeRead": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id", + "description": "The ID of the attribute" + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." + }, + "kind": { + "type": "string", + "const": "NumberPool", + "title": "Kind", + "description": "Defines the type of the attribute." + }, + "enum": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Enum", + "description": "Define a list of valid values for the attribute." + }, + "computed_attribute": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserRead" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Read" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonRead" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Read", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonRead", + "User": "#/components/schemas/ComputedAttributeUserRead" + } + } + }, + { + "type": "null" + } + ], + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." + }, + "choices": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/DropdownChoiceRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." + }, + "regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Regex", + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" + }, + "max_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" + }, + "min_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Min Length", + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" + }, + "label": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" + } + ], + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" + }, + "description": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Short description of the attribute." + }, + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", + "default": false + }, + "unique": { + "type": "boolean", + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { + "anyOf": [ + { + "$ref": "#/components/schemas/BranchSupportType" + }, + { + "type": "null" + } + ], + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." + }, + "order_weight": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." + }, + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true + }, + "default_value": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Default Value", + "description": "Default value of the attribute." + }, + "inherited": { + "type": "boolean", + "title": "Inherited", + "description": "Internal value to indicate if the attribute was inherited from a Generic node.", + "default": false + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" + }, + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + }, + "parameters": { + "anyOf": [ + { + "$ref": "#/components/schemas/NumberPoolParametersRead" + }, + { + "type": "null" + } + ], + "description": "Extra parameters specific to this kind of attribute" + } + }, + "type": "object", + "required": [ + "name", + "kind" + ], + "title": "NumberPoolAttributeRead" + }, + "NumberPoolAttributeWrite": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id", + "description": "The ID of the attribute" + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." + }, + "kind": { + "type": "string", + "const": "NumberPool", + "title": "Kind", + "description": "Defines the type of the attribute." + }, + "enum": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Enum", + "description": "Define a list of valid values for the attribute." + }, + "computed_attribute": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserWrite" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Write" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonWrite" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Write", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonWrite", + "User": "#/components/schemas/ComputedAttributeUserWrite" + } + } + }, + { + "type": "null" + } + ], + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." + }, + "choices": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/DropdownChoiceWrite" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." + }, + "regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Regex", + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" + }, + "max_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" + }, + "min_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Min Length", + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" + }, + "label": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" + } + ], + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" + }, + "description": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Short description of the attribute." + }, + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", + "default": false + }, + "unique": { + "type": "boolean", + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { + "anyOf": [ + { + "$ref": "#/components/schemas/BranchSupportType" + }, + { + "type": "null" + } + ], + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." + }, + "order_weight": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." + }, + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true + }, + "default_value": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Default Value", + "description": "Default value of the attribute." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" + }, + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + }, + "parameters": { + "anyOf": [ + { + "$ref": "#/components/schemas/NumberPoolParametersWrite" + }, + { + "type": "null" + } + ], + "description": "Extra parameters specific to this kind of attribute" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "kind" + ], + "title": "NumberPoolAttributeWrite" + }, + "NumberPoolParametersRead": { + "properties": { + "end_range": { + "type": "integer", + "title": "End Range", + "description": "End range for numbers for the associated NumberPool", + "default": 9223372036854775807 + }, + "start_range": { + "type": "integer", + "title": "Start Range", + "description": "Start range for numbers for the associated NumberPool", + "default": 1 + }, + "number_pool_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Number Pool Id", + "description": "The ID of the numberpool associated with this attribute. Only set after the number pool has been provisioned." + } + }, + "type": "object", + "title": "NumberPoolParametersRead" + }, + "NumberPoolParametersWrite": { + "properties": { + "end_range": { + "type": "integer", + "title": "End Range", + "description": "End range for numbers for the associated NumberPool", + "default": 9223372036854775807 + }, + "start_range": { + "type": "integer", + "title": "Start Range", + "description": "Start range for numbers for the associated NumberPool", + "default": 1 + }, + "number_pool_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Number Pool Id", + "description": "The ID of the numberpool associated with this attribute. Only set after the number pool has been provisioned." + } + }, + "additionalProperties": false, + "type": "object", + "title": "NumberPoolParametersWrite" + }, + "PasswordCredential": { + "properties": { + "username": { + "type": "string", + "title": "Username", + "description": "Name of the user that is logging in." + }, + "password": { + "type": "string", + "title": "Password", + "description": "The password of the user." + } + }, + "type": "object", + "required": [ + "username", + "password" + ], + "title": "PasswordCredential" + }, + "PolicySettings": { + "properties": { + "required_proposed_change_approvals": { + "type": "integer", + "minimum": 0.0, + "title": "Required Proposed Change Approvals", + "description": "Number of approvals required for proposed changes. (Enterprise only: not available in the community version.)", + "default": 0 + }, + "revoke_proposed_change_approvals": { + "type": "boolean", + "title": "Revoke Proposed Change Approvals", + "description": "Boolean indicating whether performing changes on a proposed change branch should revoke existing approvals. (Enterprise only: not available in the community version.)", + "default": false + } + }, + "additionalProperties": false, + "type": "object", + "title": "PolicySettings" + }, + "QueryPayload": { + "properties": { + "variables": { + "additionalProperties": true, + "type": "object", + "title": "Variables" + } + }, + "type": "object", + "title": "QueryPayload" + }, + "RelationshipCardinality": { + "type": "string", + "enum": [ + "one", + "many" + ], + "title": "RelationshipCardinality" + }, + "RelationshipDeleteBehavior": { + "type": "string", + "enum": [ + "no-action", + "cascade" + ], + "title": "RelationshipDeleteBehavior" + }, + "RelationshipDirection": { + "type": "string", + "enum": [ + "bidirectional", + "outbound", + "inbound" + ], + "title": "RelationshipDirection" + }, + "RelationshipKind": { + "type": "string", + "enum": [ + "Generic", + "Attribute", + "Component", + "Parent", + "Group", + "Hierarchy", + "Profile", + "Template" + ], + "title": "RelationshipKind" + }, + "RelationshipSchemaRead": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id", + "description": "The ID of the relationship schema" + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Relationship name, must be unique within a model and must be all lowercase." + }, + "peer": { + "type": "string", + "pattern": "^[A-Z][a-zA-Z0-9]+$", + "title": "Peer", + "description": "Type (kind) of objects supported on the other end of the relationship." + }, + "kind": { + "$ref": "#/components/schemas/RelationshipKind", + "description": "Defines the type of the relationship.", + "default": "Generic" + }, + "label": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" + } + ], + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" + }, + "description": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Short description of the relationship." + }, + "identifier": { + "anyOf": [ + { + "type": "string", + "maxLength": 128, + "pattern": "^[a-z0-9\\_]+$" + }, + { + "type": "null" + } + ], + "title": "Identifier", + "description": "Unique identifier of the relationship within a model, identifiers must match to traverse a relationship on both direction." + }, + "cardinality": { + "$ref": "#/components/schemas/RelationshipCardinality", + "description": "Defines how many objects are expected on the other side of the relationship.", + "default": "many" + }, + "min_count": { + "type": "integer", + "title": "Min Count", + "description": "Defines the minimum objects allowed on the other side of the relationship.", + "default": 0 + }, + "max_count": { + "type": "integer", + "title": "Max Count", + "description": "Defines the maximum objects allowed on the other side of the relationship.", + "default": 0 + }, + "common_parent": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Common Parent", + "description": "Name of a parent relationship on the peer schema that must share the same related object with the object's parent." + }, + "common_relatives": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Common Relatives", + "description": "List of relationship names on the peer schema for which all objects must share the same set of peers." + }, + "order_weight": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Order Weight", + "description": "Number used to order the relationship in the frontend (table and view). Lowest value will be ordered first." + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this relationship is mandatory or optional.", + "default": true + }, + "branch": { + "anyOf": [ + { + "$ref": "#/components/schemas/BranchSupportType" + }, + { + "type": "null" + } + ], + "description": "Type of branch support for the relationship. If not defined, it will be determined based on both peers." + }, + "inherited": { + "type": "boolean", + "title": "Inherited", + "description": "Internal value to indicate if the relationship was inherited from a Generic node.", + "default": false + }, + "direction": { + "$ref": "#/components/schemas/RelationshipDirection", + "description": "Defines the direction of the relationship, Unidirectional relationship are required when the same model is on both side.", + "default": "bidirectional" + }, + "hierarchical": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Hierarchical", + "description": "Internal attribute to track the type of hierarchy this relationship is part of, must match a valid Generic Kind" + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the relationship after loading the schema", + "default": "present" + }, + "on_delete": { + "anyOf": [ + { + "$ref": "#/components/schemas/RelationshipDeleteBehavior" + }, + { + "type": "null" + } + ], + "description": "Default is no-action. If cascade, related node(s) are deleted when this node is deleted." + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the relationship.", + "default": "any" + }, + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the relationship as read-only, users won't be able to change its value.", + "default": false + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Deprecation", + "description": "Mark relationship as deprecated and provide a user-friendly message to display" + }, + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the relationship is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + } + }, + "type": "object", + "required": [ + "name", + "peer" + ], + "title": "RelationshipSchemaRead" + }, + "RelationshipSchemaWrite": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id", + "description": "The ID of the relationship schema" + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Relationship name, must be unique within a model and must be all lowercase." + }, + "peer": { + "type": "string", + "pattern": "^[A-Z][a-zA-Z0-9]+$", + "title": "Peer", + "description": "Type (kind) of objects supported on the other end of the relationship." + }, + "kind": { + "$ref": "#/components/schemas/RelationshipKind", + "description": "Defines the type of the relationship.", + "default": "Generic" + }, + "label": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" + } + ], + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" + }, + "description": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Short description of the relationship." + }, + "identifier": { + "anyOf": [ + { + "type": "string", + "maxLength": 128, + "pattern": "^[a-z0-9\\_]+$" + }, + { + "type": "null" + } + ], + "title": "Identifier", + "description": "Unique identifier of the relationship within a model, identifiers must match to traverse a relationship on both direction." + }, + "cardinality": { + "$ref": "#/components/schemas/RelationshipCardinality", + "description": "Defines how many objects are expected on the other side of the relationship.", + "default": "many" + }, + "min_count": { + "type": "integer", + "title": "Min Count", + "description": "Defines the minimum objects allowed on the other side of the relationship.", + "default": 0 + }, + "max_count": { + "type": "integer", + "title": "Max Count", + "description": "Defines the maximum objects allowed on the other side of the relationship.", + "default": 0 + }, + "common_parent": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Common Parent", + "description": "Name of a parent relationship on the peer schema that must share the same related object with the object's parent." + }, + "common_relatives": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Common Relatives", + "description": "List of relationship names on the peer schema for which all objects must share the same set of peers." + }, + "order_weight": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Order Weight", + "description": "Number used to order the relationship in the frontend (table and view). Lowest value will be ordered first." + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this relationship is mandatory or optional.", + "default": true + }, + "branch": { + "anyOf": [ + { + "$ref": "#/components/schemas/BranchSupportType" + }, + { + "type": "null" + } + ], + "description": "Type of branch support for the relationship. If not defined, it will be determined based on both peers." + }, + "direction": { + "$ref": "#/components/schemas/RelationshipDirection", + "description": "Defines the direction of the relationship, Unidirectional relationship are required when the same model is on both side.", + "default": "bidirectional" + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the relationship after loading the schema", + "default": "present" + }, + "on_delete": { + "anyOf": [ + { + "$ref": "#/components/schemas/RelationshipDeleteBehavior" + }, + { + "type": "null" + } + ], + "description": "Default is no-action. If cascade, related node(s) are deleted when this node is deleted." + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the relationship.", + "default": "any" + }, + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the relationship as read-only, users won't be able to change its value.", + "default": false + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Deprecation", + "description": "Mark relationship as deprecated and provide a user-friendly message to display" + }, + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the relationship is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name", + "peer" + ], + "title": "RelationshipSchemaWrite" + }, + "RemoteLoggingSettings": { + "properties": { + "enable": { + "type": "boolean", + "title": "Enable", + "default": false + }, + "frontend_dsn": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Frontend Dsn" + }, + "api_server_dsn": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Api Server Dsn" + }, + "git_agent_dsn": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Git Agent Dsn" + } + }, + "additionalProperties": false, + "type": "object", + "title": "RemoteLoggingSettings" + }, + "RemoteSendStatus": { + "type": "string", + "enum": [ + "pending", + "sent", + "skipped", + "failed" + ], + "title": "RemoteSendStatus" + }, + "SSOInfo": { + "properties": { + "providers": { + "items": { + "$ref": "#/components/schemas/SSOProviderInfo" + }, + "type": "array", + "title": "Providers" + }, + "enabled": { + "type": "boolean", + "title": "Enabled", + "readOnly": true + } + }, + "type": "object", + "required": [ + "enabled" + ], + "title": "SSOInfo" + }, + "SSOProtocol": { + "type": "string", + "enum": [ + "oauth2", + "oidc" + ], + "title": "SSOProtocol" + }, + "SSOProviderInfo": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "display_label": { + "type": "string", + "title": "Display Label" + }, + "icon": { + "type": "string", + "title": "Icon" + }, + "protocol": { + "$ref": "#/components/schemas/SSOProtocol" + }, + "authorize_path": { + "type": "string", + "title": "Authorize Path", + "readOnly": true + }, + "token_path": { + "type": "string", + "title": "Token Path", + "readOnly": true + } + }, + "type": "object", + "required": [ + "name", + "display_label", + "icon", + "protocol", + "authorize_path", + "token_path" + ], + "title": "SSOProviderInfo" + }, + "SchemaAttributeDisplay": { + "type": "string", + "enum": [ + "default", + "extra" + ], + "title": "SchemaAttributeDisplay" + }, + "SchemaBranchHash": { + "properties": { + "main": { + "type": "string", + "title": "Main" + }, + "nodes": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "title": "Nodes" }, "generics": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "title": "Generics" + } + }, + "type": "object", + "required": [ + "main" + ], + "title": "SchemaBranchHash" + }, + "SchemaDiff": { + "properties": { + "added": { + "additionalProperties": { + "$ref": "#/components/schemas/HashableModelDiff" + }, + "type": "object", + "title": "Added" + }, + "changed": { + "additionalProperties": { + "$ref": "#/components/schemas/HashableModelDiff" + }, + "type": "object", + "title": "Changed" + }, + "removed": { + "additionalProperties": { + "$ref": "#/components/schemas/HashableModelDiff" + }, + "type": "object", + "title": "Removed" + } + }, + "additionalProperties": false, + "type": "object", + "title": "SchemaDiff" + }, + "SchemaExtensionWrite": { + "properties": { + "nodes": { "items": { - "$ref": "#/components/schemas/GenericSchema" + "$ref": "#/components/schemas/NodeExtensionWrite" }, "type": "array", - "title": "Generics" + "title": "Nodes", + "description": "Nodes to extend with additional attributes and relationships." + } + }, + "additionalProperties": false, + "type": "object", + "title": "SchemaExtensionWrite" + }, + "SchemaLoadAPI": { + "properties": { + "version": { + "type": "string", + "title": "Version" }, "nodes": { "items": { - "$ref": "#/components/schemas/NodeSchema" + "$ref": "#/components/schemas/NodeSchemaWrite" }, "type": "array", "title": "Nodes" }, + "generics": { + "items": { + "$ref": "#/components/schemas/GenericSchemaWrite" + }, + "type": "array", + "title": "Generics" + }, "extensions": { - "$ref": "#/components/schemas/SchemaExtension", - "default": { - "state": "present", - "nodes": [] - } + "anyOf": [ + { + "$ref": "#/components/schemas/SchemaExtensionWrite" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -6671,6 +8576,14 @@ ], "title": "SchemaReadAPI" }, + "SchemaState": { + "type": "string", + "enum": [ + "present", + "absent" + ], + "title": "SchemaState" + }, "SchemaUpdate": { "properties": { "hash": { @@ -6725,27 +8638,187 @@ "title": "Kinds", "description": "The kinds impacted by the warning" }, - "message": { - "type": "string", - "title": "Message", - "description": "The message that describes the warning" + "message": { + "type": "string", + "title": "Message", + "description": "The message that describes the warning" + } + }, + "type": "object", + "required": [ + "type", + "message" + ], + "title": "SchemaWarning" + }, + "SchemaWarningKind": { + "properties": { + "kind": { + "type": "string", + "title": "Kind", + "description": "The kind impacted by the warning" + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Field", + "description": "The attribute or relationship impacted by the warning" + } + }, + "type": "object", + "required": [ + "kind" + ], + "title": "SchemaWarningKind" + }, + "SchemaWarningType": { + "type": "string", + "enum": [ + "deprecation" + ], + "title": "SchemaWarningType" + }, + "SchemasLoadAPI": { + "properties": { + "schemas": { + "items": { + "$ref": "#/components/schemas/SchemaLoadAPI" + }, + "type": "array", + "title": "Schemas" + } + }, + "type": "object", + "required": [ + "schemas" + ], + "title": "SchemasLoadAPI" + }, + "TelemetrySnapshotListResponse": { + "properties": { + "count": { + "type": "integer", + "title": "Count" + }, + "snapshots": { + "items": { + "$ref": "#/components/schemas/TelemetrySnapshotResponse" + }, + "type": "array", + "title": "Snapshots" + } + }, + "type": "object", + "required": [ + "count", + "snapshots" + ], + "title": "TelemetrySnapshotListResponse" + }, + "TelemetrySnapshotResponse": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "created_at": { + "type": "string", + "title": "Created At" + }, + "kind": { + "type": "string", + "title": "Kind" + }, + "payload_format": { + "type": "string", + "title": "Payload Format" + }, + "deployment_id": { + "type": "string", + "title": "Deployment Id" + }, + "infrahub_version": { + "type": "string", + "title": "Infrahub Version" + }, + "data": { + "additionalProperties": true, + "type": "object", + "title": "Data" + }, + "checksum": { + "type": "string", + "title": "Checksum" + }, + "remote_send_status": { + "$ref": "#/components/schemas/RemoteSendStatus" + } + }, + "type": "object", + "required": [ + "id", + "created_at", + "kind", + "payload_format", + "deployment_id", + "infrahub_version", + "data", + "checksum", + "remote_send_status" + ], + "title": "TelemetrySnapshotResponse" + }, + "TextAttributeParametersRead": { + "properties": { + "regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Regex", + "description": "Regular expression that attribute value must match if defined" + }, + "min_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Min Length", + "description": "Set a minimum number of characters allowed." + }, + "max_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Length", + "description": "Set a maximum number of characters allowed." } }, "type": "object", - "required": [ - "type", - "message" - ], - "title": "SchemaWarning" + "title": "TextAttributeParametersRead" }, - "SchemaWarningKind": { + "TextAttributeParametersWrite": { "properties": { - "kind": { - "type": "string", - "title": "Kind", - "description": "The kind impacted by the warning" - }, - "field": { + "regex": { "anyOf": [ { "type": "string" @@ -6754,114 +8827,300 @@ "type": "null" } ], - "title": "Field", - "description": "The attribute or relationship impacted by the warning" - } - }, - "type": "object", - "required": [ - "kind" - ], - "title": "SchemaWarningKind" - }, - "SchemaWarningType": { - "type": "string", - "enum": [ - "deprecation" - ], - "title": "SchemaWarningType" - }, - "SchemasLoadAPI": { - "properties": { - "schemas": { - "items": { - "$ref": "#/components/schemas/SchemaLoadAPI" - }, - "type": "array", - "title": "Schemas" - } - }, - "type": "object", - "required": [ - "schemas" - ], - "title": "SchemasLoadAPI" - }, - "TelemetrySnapshotListResponse": { - "properties": { - "count": { - "type": "integer", - "title": "Count" + "title": "Regex", + "description": "Regular expression that attribute value must match if defined" }, - "snapshots": { - "items": { - "$ref": "#/components/schemas/TelemetrySnapshotResponse" - }, - "type": "array", - "title": "Snapshots" + "min_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Min Length", + "description": "Set a minimum number of characters allowed." + }, + "max_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Length", + "description": "Set a maximum number of characters allowed." } }, + "additionalProperties": false, "type": "object", - "required": [ - "count", - "snapshots" - ], - "title": "TelemetrySnapshotListResponse" + "title": "TextAttributeParametersWrite" }, - "TelemetrySnapshotResponse": { + "TextAttributeRead": { "properties": { "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id", + "description": "The ID of the attribute" + }, + "name": { "type": "string", - "title": "Id" + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." + }, + "kind": { + "type": "string", + "enum": [ + "Text", + "TextArea" + ], + "title": "Kind", + "description": "Defines the type of the attribute." + }, + "enum": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Enum", + "description": "Define a list of valid values for the attribute." + }, + "computed_attribute": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserRead" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Read" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonRead" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Read", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonRead", + "User": "#/components/schemas/ComputedAttributeUserRead" + } + } + }, + { + "type": "null" + } + ], + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." + }, + "choices": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/DropdownChoiceRead" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." + }, + "regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Regex", + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" + }, + "max_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" + }, + "min_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Min Length", + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" + }, + "label": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" + } + ], + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" + }, + "description": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Short description of the attribute." + }, + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", + "default": false + }, + "unique": { + "type": "boolean", + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { + "anyOf": [ + { + "$ref": "#/components/schemas/BranchSupportType" + }, + { + "type": "null" + } + ], + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." + }, + "order_weight": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." }, - "created_at": { - "type": "string", - "title": "Created At" + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true }, - "kind": { - "type": "string", - "title": "Kind" + "default_value": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Default Value", + "description": "Default value of the attribute." }, - "payload_format": { - "type": "string", - "title": "Payload Format" + "inherited": { + "type": "boolean", + "title": "Inherited", + "description": "Internal value to indicate if the attribute was inherited from a Generic node.", + "default": false }, - "deployment_id": { - "type": "string", - "title": "Deployment Id" + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" }, - "infrahub_version": { - "type": "string", - "title": "Infrahub Version" + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" }, - "data": { - "additionalProperties": true, - "type": "object", - "title": "Data" + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" }, - "checksum": { - "type": "string", - "title": "Checksum" + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" }, - "remote_send_status": { - "$ref": "#/components/schemas/RemoteSendStatus" + "parameters": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextAttributeParametersRead" + }, + { + "type": "null" + } + ], + "description": "Extra parameters specific to this kind of attribute" } }, "type": "object", "required": [ - "id", - "created_at", - "kind", - "payload_format", - "deployment_id", - "infrahub_version", - "data", - "checksum", - "remote_send_status" + "name", + "kind" ], - "title": "TelemetrySnapshotResponse" + "title": "TextAttributeRead" }, - "TextAttributeParameters": { + "TextAttributeWrite": { "properties": { "id": { "anyOf": [ @@ -6872,11 +9131,83 @@ "type": "null" } ], - "title": "Id" + "title": "Id", + "description": "The ID of the attribute" }, - "state": { - "$ref": "#/components/schemas/HashableModelState", - "default": "present" + "name": { + "type": "string", + "maxLength": 64, + "minLength": 3, + "pattern": "^[a-z0-9\\_]+$", + "title": "Name", + "description": "Attribute name, must be unique within a model and must be all lowercase." + }, + "kind": { + "type": "string", + "enum": [ + "Text", + "TextArea" + ], + "title": "Kind", + "description": "Defines the type of the attribute." + }, + "enum": { + "anyOf": [ + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Enum", + "description": "Define a list of valid values for the attribute." + }, + "computed_attribute": { + "anyOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/ComputedAttributeUserWrite" + }, + { + "$ref": "#/components/schemas/ComputedAttributeJinja2Write" + }, + { + "$ref": "#/components/schemas/ComputedAttributeTransformPythonWrite" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "Jinja2": "#/components/schemas/ComputedAttributeJinja2Write", + "TransformPython": "#/components/schemas/ComputedAttributeTransformPythonWrite", + "User": "#/components/schemas/ComputedAttributeUserWrite" + } + } + }, + { + "type": "null" + } + ], + "title": "Computed Attribute", + "description": "Defines how the value of this attribute will be populated." + }, + "choices": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/DropdownChoiceWrite" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Choices", + "description": "Define a list of valid choices for a dropdown attribute." }, "regex": { "anyOf": [ @@ -6888,8 +9219,19 @@ } ], "title": "Regex", - "description": "Regular expression that attribute value must match if defined", - "update": "validate_constraint" + "description": "Regex uses to limit the characters allowed in for the attributes. (deprecated: please use parameters.regex instead)" + }, + "max_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Length", + "description": "Set a maximum number of characters allowed for a given attribute. (deprecated: please use parameters.max_length instead)" }, "min_length": { "anyOf": [ @@ -6901,10 +9243,64 @@ } ], "title": "Min Length", - "description": "Set a minimum number of characters allowed.", - "update": "validate_constraint" + "description": "Set a minimum number of characters allowed for a given attribute. (deprecated: please use parameters.min_length instead)" }, - "max_length": { + "label": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" + } + ], + "title": "Label", + "description": "Human friendly representation of the name. Will be autogenerated if not provided" + }, + "description": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Short description of the attribute." + }, + "read_only": { + "type": "boolean", + "title": "Read Only", + "description": "Set the attribute as Read-Only, users won't be able to change its value. Mainly relevant for internal object.", + "default": false + }, + "unique": { + "type": "boolean", + "title": "Unique", + "description": "Indicate if the value of this attribute must be unique in the database for a given model.", + "default": false + }, + "optional": { + "type": "boolean", + "title": "Optional", + "description": "Indicate if this attribute is mandatory or optional.", + "default": false + }, + "branch": { + "anyOf": [ + { + "$ref": "#/components/schemas/BranchSupportType" + }, + { + "type": "null" + } + ], + "description": "Type of branch support for the attribute, if not defined it will be inherited from the node." + }, + "order_weight": { "anyOf": [ { "type": "integer" @@ -6913,14 +9309,72 @@ "type": "null" } ], - "title": "Max Length", - "description": "Set a maximum number of characters allowed.", - "update": "validate_constraint" + "title": "Order Weight", + "description": "Number used to order the attribute in the frontend (table and view). Lowest value will be ordered first." + }, + "ordered": { + "type": "boolean", + "title": "Ordered", + "description": "Whether element order is significant. When False, reordering a List or JSON-array attribute is not a merge/rebase conflict.", + "default": true + }, + "default_value": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Default Value", + "description": "Default value of the attribute." + }, + "state": { + "$ref": "#/components/schemas/SchemaState", + "description": "Expected state of the attribute after loading the schema", + "default": "present" + }, + "allow_override": { + "$ref": "#/components/schemas/AllowOverrideType", + "description": "Type of allowed override for the attribute.", + "default": "any" + }, + "deprecation": { + "anyOf": [ + { + "type": "string", + "maxLength": 128 + }, + { + "type": "null" + } + ], + "title": "Deprecation", + "description": "Mark attribute as deprecated and provide a user-friendly message to display" + }, + "display": { + "$ref": "#/components/schemas/SchemaAttributeDisplay", + "description": "Controls where the attribute is displayed. 'default' shows in the main view, 'extra' shows in an expanded/secondary section.", + "default": "default" + }, + "parameters": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextAttributeParametersWrite" + }, + { + "type": "null" + } + ], + "description": "Extra parameters specific to this kind of attribute" } }, "additionalProperties": false, "type": "object", - "title": "TextAttributeParameters" + "required": [ + "name", + "kind" + ], + "title": "TextAttributeWrite" }, "UploadContentPayload": { "properties": { diff --git a/tasks/backend.py b/tasks/backend.py index 8bd3d6912f6..4db576404e3 100644 --- a/tasks/backend.py +++ b/tasks/backend.py @@ -1,9 +1,16 @@ +from dataclasses import dataclass from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any from invoke import Context, task from invoke.runners import Result +if TYPE_CHECKING: + from jinja2 import Template + + from infrahub.core.constants import Visibility + from infrahub.core.schema.definitions.internal import SchemaNode + from .shared import ( INFRAHUB_DATABASE, NBR_WORKERS, @@ -298,8 +305,8 @@ def _generate_custom_graphql_types(context: Context) -> None: context=context, command=f"uv run infrahubctl graphql generate-return-types {gql_file} --schema schema/schema.graphql", ) - execute_command(context=context, command=f"uv run ruff check --fix {Path(gql_file).parent}") - execute_command(context=context, command=f"uv run ruff format {Path(gql_file).parent}") + execute_command(context=context, command=f'uv run ruff check --fix "{Path(gql_file).parent}"') + execute_command(context=context, command=f'uv run ruff format "{Path(gql_file).parent}"') @task @@ -316,11 +323,23 @@ def validate_generated(context: Context, docker: bool = False) -> None: # noqa: with context.cd(ESCAPED_REPO_PATH): context.run(exec_cmd) + # The user-facing schema models live in the Python SDK submodule but are generated here from + # the same internal definitions. `git diff` from the superproject only tracks the submodule + # pointer, so the diff must run inside the submodule to see the generated files themselves. + exec_cmd = "git -C python_sdk diff --exit-code infrahub_sdk/schema/generated" + with context.cd(ESCAPED_REPO_PATH): + context.run(exec_cmd) + _generate_protocols(context=context) exec_cmd = "git diff --exit-code backend/infrahub/core/protocols.py backend/tests/protocols.py" with context.cd(ESCAPED_REPO_PATH): context.run(exec_cmd) + # The SDK protocols are likewise generated into the submodule from the core models here. + exec_cmd = "git -C python_sdk diff --exit-code infrahub_sdk/protocols.py" + with context.cd(ESCAPED_REPO_PATH): + context.run(exec_cmd) + _generate_custom_graphql_types(context=context) exec_cmd = "git diff --exit-code backend/infrahub/generators/graphql_queries/ backend/infrahub/computed_attribute/graphql_queries/" with context.cd(ESCAPED_REPO_PATH): @@ -379,8 +398,597 @@ def _generate_schemas(context: Context) -> None: relationship_schema_output = f"{generated}/relationship_schema.py" Path(relationship_schema_output).write_text(relationship_rendered, encoding="utf-8") - execute_command(context=context, command=f"ruff format {generated}") - execute_command(context=context, command=f"ruff check --fix {generated}") + execute_command(context=context, command=f'ruff format "{generated}"') + execute_command(context=context, command=f'ruff check --fix "{generated}"') + + _generate_schemas_sdk(context=context) + + +def _attribute_kinds_by_parameters(expected_parameters: set[str]) -> dict[str, list[str]]: + """Group attribute kinds by the parameters model the backend selects for each. + + Deriving the grouping from the backend mapping keeps the generated discriminated union in + step with it; the caller supplies the set of parameters models its variants cover. + + Raises: + ValueError: If the backend mapping yields a parameters model set that differs from + ``expected_parameters``, so the variant definitions are updated in lockstep. + + """ + from infrahub.core.schema.attribute_parameters import get_attribute_parameters_class_for_kind + from infrahub.types import ATTRIBUTE_KIND_LABELS + + groups: dict[str, list[str]] = {} + for attribute_kind in ATTRIBUTE_KIND_LABELS: + groups.setdefault(get_attribute_parameters_class_for_kind(attribute_kind).__name__, []).append(attribute_kind) + if set(groups) != expected_parameters: + raise ValueError( + "Attribute parameters mapping changed; update attribute_variant_specs to match: " + f"{sorted(groups)} != {sorted(expected_parameters)}" + ) + return groups + + +def _sdk_extension_field(name: str, annotation: str, default_definition: str, description: str) -> dict[str, str]: + return { + "name": name, + "external_type_annotation": annotation, + "external_default_definition": default_definition, + "description": description, + "external_pattern": "", + "min": "", + "max": "", + } + + +def _sdk_extension_families(suffix: str) -> list[dict[str, Any]]: + """Write-only models describing an extension of an existing node. + + Extension nodes are addressed by kind and carry the attributes and relationships to add, + so they reuse the discriminated attribute union and the relationship model of the variant. + """ + node_extension_fields = [ + _sdk_extension_field("kind", "str", "...", "Kind of the existing node to extend."), + _sdk_extension_field( + "attributes", + f"list[AttributeSchema{suffix}]", + "default_factory=list", + "Attributes to add to the existing node.", + ), + _sdk_extension_field( + "relationships", + f"list[RelationshipSchema{suffix}]", + "default_factory=list", + "Relationships to add to the existing node.", + ), + ] + return [ + {"class_name": f"NodeExtension{suffix}", "parent": "BaseModel", "attributes": node_extension_fields}, + { + "class_name": f"SchemaExtension{suffix}", + "parent": "BaseModel", + "attributes": [ + _sdk_extension_field( + "nodes", + f"list[NodeExtension{suffix}]", + "default_factory=list", + "Nodes to extend with additional attributes and relationships.", + ), + ], + }, + ] + + +def _sdk_root(suffix: str, with_version: bool, model_config_args: str, with_extensions: bool) -> dict[str, Any]: + config_field = [f"model_config = ConfigDict({model_config_args})"] if model_config_args else [] + version_field = ["version: str | None = None"] if with_version else [] + extensions_field = [f"extensions: SchemaExtension{suffix} | None = None"] if with_extensions else [] + fields = [ + *config_field, + *version_field, + f"nodes: list[NodeSchema{suffix}] = Field(default_factory=list)", + f"generics: list[GenericSchema{suffix}] = Field(default_factory=list)", + *extensions_field, + ] + return {"class_name": f"InfrahubSchema{suffix}", "fields": fields} + + +def _sdk_base_node_family(base_node_schema: "SchemaNode", minimum: "Visibility", suffix: str) -> dict[str, Any]: + """Base node family shared by node/generic/profile/template. + + Every node model exposes the derived ``kind`` (from namespace+name) on both write and read so + attribute access works when reading a locally-authored schema. It only *serializes* on read: + on write it is a plain property (not part of the payload), so ``kind`` never round-trips into + the write contract where ``extra="forbid"`` would reject it. The read variant additionally + carries the server-computed ``hash`` field. All propagate to node/generic/profile/template via + this base. + """ + from infrahub.core.constants import UpdateSupport, Visibility + from infrahub.core.schema.definitions.internal import SchemaAttribute + + attributes = [attribute for attribute in base_node_schema.attributes if attribute.visibility >= minimum] + family: dict[str, Any] = { + "class_name": f"BaseNodeSchema{suffix}", + "parent": "BaseModel", + "attributes": attributes, + "computed_fields": [ + { + "name": "kind", + "return_type": "str", + "expression": 'f"{self.namespace}{self.name}"', + "serialize": minimum == Visibility.READ, + } + ], + } + if minimum == Visibility.READ: + # `hash` is computed by the server and only exposed on read. + hash_field = SchemaAttribute( + name="hash", + kind="Text", + internal_kind=str, + description="Hash of the node computed by the server.", + optional=True, + extra={"update": UpdateSupport.NOT_SUPPORTED, "visibility": Visibility.READ}, + ) + family["attributes"] = [*attributes, hash_field] + return family + + +def _sdk_profile_template_families(suffix: str) -> list[dict[str, Any]]: + """Read-only node families for profiles and templates (no write variant exists). + + Both are a base node plus the list of generics they inherit from, mirroring their internal + counterparts; only the extra field is declared, the rest comes from the parent. + """ + from infrahub.core.constants import UpdateSupport, Visibility + from infrahub.core.schema.definitions.internal import SchemaAttribute + + def inherit_from(subject: str) -> SchemaAttribute: + return SchemaAttribute( + name="inherit_from", + kind="List", + internal_kind=str, + default_factory="list", + description=f"List of Generic Kind that this {subject} is inheriting from", + optional=True, + extra={"update": UpdateSupport.ALLOWED, "visibility": Visibility.READ}, + ) + + return [ + { + "class_name": f"ProfileSchema{suffix}", + "parent": f"BaseNodeSchema{suffix}", + "attributes": [inherit_from("profile")], + }, + { + "class_name": f"TemplateSchema{suffix}", + "parent": f"BaseNodeSchema{suffix}", + "attributes": [inherit_from("template")], + }, + ] + + +# Values are sourced from ATTRIBUTE_KIND_LABELS to stay in step with the backend; the SDK's +# historical AttributeKind member names (e.g. MAC_ADDRESS) do not follow a single rule, so they +# are mapped explicitly here. +_ATTRIBUTE_KIND_MEMBER_NAMES = { + "ID": "ID", + "Dropdown": "DROPDOWN", + "Text": "TEXT", + "TextArea": "TEXTAREA", + "DateTime": "DATETIME", + "Email": "EMAIL", + "Password": "PASSWORD", + "HashedPassword": "HASHEDPASSWORD", + "URL": "URL", + "File": "FILE", + "MacAddress": "MAC_ADDRESS", + "Color": "COLOR", + "Number": "NUMBER", + "NumberPool": "NUMBERPOOL", + "Bandwidth": "BANDWIDTH", + "IPHost": "IPHOST", + "IPNetwork": "IPNETWORK", + "Boolean": "BOOLEAN", + "Checkbox": "CHECKBOX", + "List": "LIST", + "JSON": "JSON", + "Any": "ANY", +} + + +@dataclass(frozen=True) +class SdkEnumSpecs: + """The (str, Enum) specs and discriminator lookups used to type the constrained SDK fields. + + ``enum_specs`` is the ordered ``name -> [(member, value)]`` mapping (its order sets the class + order in the generated enums.py); the two ``*_value_to_member`` lookups map enum values back to + member names for the attribute-kind and computed-attribute-kind discriminated unions. + """ + + enum_specs: dict[str, list[tuple[str, str]]] + attribute_kind_value_to_member: dict[str, str] + computed_kind_value_to_member: dict[str, str] + + +def _sdk_enum_specs() -> SdkEnumSpecs: + """Build the dedicated (str, Enum) specs used to type the constrained SDK fields.""" + from enum import Enum + + from infrahub.core.constants import ( + AllowOverrideType, + BranchSupportType, + ComputedAttributeKind, + HashableModelState, + RelationshipCardinality, + RelationshipDeleteBehavior, + RelationshipDirection, + RelationshipKind, + SchemaAttributeDisplay, + ) + from infrahub.types import ATTRIBUTE_KIND_LABELS + + def members(enum_cls: type[Enum]) -> list[tuple[str, str]]: + return [(member.name, member.value) for member in enum_cls] + + attribute_kind_members = [(_ATTRIBUTE_KIND_MEMBER_NAMES[value], value) for value in ATTRIBUTE_KIND_LABELS] + enum_specs: dict[str, list[tuple[str, str]]] = { + "BranchSupportType": members(BranchSupportType), + "RelationshipKind": members(RelationshipKind), + "RelationshipCardinality": members(RelationshipCardinality), + "RelationshipDirection": members(RelationshipDirection), + "RelationshipDeleteBehavior": members(RelationshipDeleteBehavior), + "AllowOverrideType": members(AllowOverrideType), + "SchemaState": members(HashableModelState), + "SchemaAttributeDisplay": members(SchemaAttributeDisplay), + "ComputedAttributeKind": members(ComputedAttributeKind), + "AttributeKind": attribute_kind_members, + } + return SdkEnumSpecs( + enum_specs=enum_specs, + attribute_kind_value_to_member={value: member for member, value in attribute_kind_members}, + computed_kind_value_to_member={value: member for member, value in members(ComputedAttributeKind)}, + ) + + +def _sdk_kind_field( + description: str, class_name: str, values: list[str], value_to_member: dict[str, str] +) -> dict[str, str]: + """Render a discriminated-union ``kind`` field as a Literal of enum members. + + A Literal of enum members keeps the discriminator tied to the shared enum while still + validating correctly under ``use_enum_values=True``. + """ + members = ", ".join(f"{class_name}.{value_to_member[value]}" for value in values) + return { + "name": "kind", + "external_type_annotation": f"Literal[{members}]", + "external_default_definition": "...", + "description": description, + "external_pattern": "", + "min": "", + "max": "", + } + + +def _render_sdk_enums(template: "Template", enum_specs: dict[str, list[tuple[str, str]]], generated: str) -> list[str]: + """Render the self-contained enums module and return the sorted enum class names.""" + enums_rendered = template.render( + enums=[ + {"name": name, "members": [(member, repr(value)) for member, value in members]} + for name, members in enum_specs.items() + ] + ) + Path(f"{generated}/enums.py").write_text(enums_rendered, encoding="utf-8") + return sorted(enum_specs) + + +def _write_sdk_generated_init(generated: str) -> None: + init_content = ( + '# Generated by "invoke backend.generate", do not edit directly\n' + "from . import enums, read, write\n\n" + '__all__ = ["enums", "read", "write"]\n' + ) + Path(f"{generated}/__init__.py").write_text(init_content, encoding="utf-8") + + +def _generate_schemas_sdk(context: Context) -> None: + """Render the user-facing write/read schema models into the Python SDK. + + Both variants are produced from the same ``internal.py`` definitions by filtering each + field on its ``visibility`` classification (write ⊆ read ⊆ internal). The output is + self-contained (only pydantic + typing) so it imports with just the SDK installed. + """ + import sys + + from jinja2 import Environment, FileSystemLoader, StrictUndefined + + from infrahub.core.constants import ComputedAttributeKind, UpdateSupport, Visibility + from infrahub.core.schema.definitions.internal import ( + SchemaAttribute, + SchemaNode, + attribute_schema, + base_node_schema, + generic_schema, + node_schema, + relationship_schema, + ) + + env = Environment(loader=FileSystemLoader(f"{REPO_BASE}/backend/templates"), undefined=StrictUndefined) + template = env.get_template("generate_schema_sdk.j2") + enums_template = env.get_template("generate_schema_sdk_enums.j2") + generated = f"{REPO_BASE}/python_sdk/infrahub_sdk/schema/generated" + Path(generated).mkdir(parents=True, exist_ok=True) + + sdk_enums = _sdk_enum_specs() + + node_stripped = node_schema.without_duplicates(base_node_schema) + generic_stripped = generic_schema.without_duplicates(base_node_schema) + + write_extra = {"update": UpdateSupport.ALLOWED, "visibility": Visibility.WRITE} + + def _field( + name: str, + kind: str, + description: str, + *, + optional: bool = False, + regex: str | None = None, + enum: list[str] | None = None, + default_value: int | None = None, + ) -> SchemaAttribute: + return SchemaAttribute( + name=name, + kind=kind, + description=description, + extra=write_extra, + optional=optional, + regex=regex, + enum=enum, + default_value=default_value, + ) + + # The attribute sub-blocks (choices, parameters, computed_attribute) are value models of their + # own. They are emitted as dedicated data models so the write contract is explicit rather than + # an opaque mapping. The attribute itself is a discriminated union on its kind: each variant + # narrows kind to the kinds sharing one parameters shape and carries that parameters model, so a + # given kind only validates against its own parameters. computed_attribute discriminates on its + # own kind field. + dropdown_choice_fields = [ + _field("name", "Text", "Name of the choice, must be unique within the dropdown."), + _field("description", "Text", "Description of the choice.", optional=True), + _field( + "color", + "Text", + "Color of the choice, must be a valid HTML color code.", + optional=True, + regex=r"#[0-9a-fA-F]{6}\b", + ), + _field("label", "Text", "Human friendly representation of the choice.", optional=True), + ] + + list_parameters_fields = [ + _field("regex", "Text", "Regular expression that each list item value must match if defined", optional=True), + ] + text_parameters_fields = [ + _field("regex", "Text", "Regular expression that attribute value must match if defined", optional=True), + _field("min_length", "Number", "Set a minimum number of characters allowed.", optional=True), + _field("max_length", "Number", "Set a maximum number of characters allowed.", optional=True), + ] + number_parameters_fields = [ + _field("min_value", "Number", "Set a minimum value allowed.", optional=True), + _field("max_value", "Number", "Set a maximum value allowed.", optional=True), + _field( + "excluded_values", + "Text", + "List of values or range of values not allowed for the attribute, format is: '100,150-200,280,300-400'", + optional=True, + regex=r"^(\d+(?:-\d+)?)(?:,\d+(?:-\d+)?)*$", + ), + ] + number_pool_parameters_fields = [ + _field("end_range", "Number", "End range for numbers for the associated NumberPool", default_value=sys.maxsize), + _field("start_range", "Number", "Start range for numbers for the associated NumberPool", default_value=1), + _field( + "number_pool_id", + "Text", + "The ID of the numberpool associated with this attribute. " + "Only set after the number pool has been provisioned.", + optional=True, + ), + ] + + computed_kind_description = "Defines how the value of the attribute is computed." + + def _computed_kind_field(value: str) -> dict[str, str]: + return _sdk_kind_field( + computed_kind_description, "ComputedAttributeKind", [value], sdk_enums.computed_kind_value_to_member + ) + + computed_user_fields = [ + _computed_kind_field(ComputedAttributeKind.USER.value), + ] + computed_jinja2_fields = [ + _computed_kind_field(ComputedAttributeKind.JINJA2.value), + _field("jinja2_template", "Text", "Jinja2 template used to compute the value, required when kind is Jinja2."), + ] + computed_transform_fields = [ + _computed_kind_field(ComputedAttributeKind.TRANSFORM_PYTHON.value), + _field("transform", "Text", "Python transform name or ID, required when kind is TransformPython."), + ] + + # The attribute is modelled as a discriminated union on kind. Each variant narrows kind to the + # set of kinds the backend maps to one parameters shape, and carries that parameters model. The + # kind -> parameters split is derived from the backend mapping so the union stays in step with + # it; the generic variant absorbs every kind not claimed by a specific one. Order sets the union + # member order. Second element is the backend parameters class name (SDK class = name + suffix). + attribute_variant_specs: list[tuple[str, str]] = [ + ("TextAttribute", "TextAttributeParameters"), + ("NumberAttribute", "NumberAttributeParameters"), + ("ListAttribute", "ListAttributeParameters"), + ("NumberPoolAttribute", "NumberPoolParameters"), + ("GenericAttribute", "AttributeParameters"), + ] + kinds_by_parameters = _attribute_kinds_by_parameters({parameters for _, parameters in attribute_variant_specs}) + + kind_description = next(attr for attr in attribute_schema.attributes if attr.name == "kind").description + parameters_source = next(attr for attr in attribute_schema.attributes if attr.name == "parameters") + + def _visible(node: SchemaNode, minimum: Visibility) -> list[Any]: + return [attribute for attribute in node.attributes if attribute.visibility >= minimum] + + def _parameters_field(parameters_name: str) -> dict[str, str]: + return { + "name": "parameters", + "external_type_annotation": f"{parameters_name}__VARIANT__ | None", + "external_default_definition": parameters_source.external_default_definition, + "description": parameters_source.description, + "external_pattern": parameters_source.external_pattern, + "min": parameters_source.min, + "max": parameters_source.max, + } + + def _attribute_variant_families(minimum: Visibility, suffix: str) -> list[dict[str, Any]]: + base_name = f"AttributeSchemaBase{suffix}" + base_fields = [attribute for attribute in _visible(attribute_schema, minimum) if attribute.name != "parameters"] + families: list[dict[str, Any]] = [{"class_name": base_name, "parent": "BaseModel", "attributes": base_fields}] + for variant, parameters_name in attribute_variant_specs: + kind_field = _sdk_kind_field( + kind_description, + "AttributeKind", + kinds_by_parameters[parameters_name], + sdk_enums.attribute_kind_value_to_member, + ) + families.append( + { + "class_name": f"{variant}{suffix}", + "parent": base_name, + "attributes": [kind_field, _parameters_field(parameters_name)], + } + ) + return families + + def _pre_families(minimum: Visibility, suffix: str) -> list[dict[str, Any]]: + base = f"AttributeParameters{suffix}" + return [ + {"class_name": base, "parent": "BaseModel", "attributes": []}, + {"class_name": f"ListAttributeParameters{suffix}", "parent": base, "attributes": list_parameters_fields}, + {"class_name": f"TextAttributeParameters{suffix}", "parent": base, "attributes": text_parameters_fields}, + { + "class_name": f"NumberAttributeParameters{suffix}", + "parent": base, + "attributes": number_parameters_fields, + }, + { + "class_name": f"NumberPoolParameters{suffix}", + "parent": base, + "attributes": number_pool_parameters_fields, + }, + {"class_name": f"DropdownChoice{suffix}", "parent": "BaseModel", "attributes": dropdown_choice_fields}, + { + "class_name": f"ComputedAttributeUser{suffix}", + "parent": "BaseModel", + "attributes": computed_user_fields, + }, + { + "class_name": f"ComputedAttributeJinja2{suffix}", + "parent": "BaseModel", + "attributes": computed_jinja2_fields, + }, + { + "class_name": f"ComputedAttributeTransformPython{suffix}", + "parent": "BaseModel", + "attributes": computed_transform_fields, + }, + *_attribute_variant_families(minimum, suffix), + ] + + def _aliases(suffix: str) -> list[str]: + variant_members = "".join(f" {variant}{suffix},\n" for variant, _ in attribute_variant_specs) + return [ + ( + f"ComputedAttribute{suffix} = Annotated[\n" + f" Union[\n" + f" ComputedAttributeUser{suffix},\n" + f" ComputedAttributeJinja2{suffix},\n" + f" ComputedAttributeTransformPython{suffix},\n" + f" ],\n" + f' Field(discriminator="kind"),\n' + "]" + ), + ( + f"AttributeSchema{suffix} = Annotated[\n" + f" Union[\n" + f"{variant_members}" + f" ],\n" + f' Field(discriminator="kind"),\n' + "]" + ), + ] + + def _families(minimum: Visibility, suffix: str) -> list[dict[str, Any]]: + return [ + { + "class_name": f"RelationshipSchema{suffix}", + "parent": "BaseModel", + "attributes": _visible(relationship_schema, minimum), + }, + _sdk_base_node_family(base_node_schema, minimum, suffix), + { + "class_name": f"NodeSchema{suffix}", + "parent": f"BaseNodeSchema{suffix}", + "attributes": _visible(node_stripped, minimum), + }, + { + "class_name": f"GenericSchema{suffix}", + "parent": f"BaseNodeSchema{suffix}", + "attributes": _visible(generic_stripped, minimum), + }, + ] + + # use_enum_values keeps runtime field values as plain strings even though fields are typed + # with the dedicated enums, so equality against strings and serialization stay unchanged. + enum_names = _render_sdk_enums(enums_template, sdk_enums.enum_specs, generated) + + # Each variant carries an extra-families builder for the models unique to it: the write + # variant adds the extension models, the read variant adds the profile/template projections. + # Both variants expose ``kind`` as a property, but only the read variant serializes it (via + # ``@computed_field``), so only the read variant imports ``computed_field``. + variants = { + "write": ( + Visibility.WRITE, + 'extra="forbid", use_enum_values=True', + "Write", + True, + _sdk_extension_families, + False, + ), + "read": (Visibility.READ, "use_enum_values=True", "Read", False, _sdk_profile_template_families, True), + } + for variant, ( + minimum, + model_config_args, + suffix, + with_version, + extra_families, + with_computed_field, + ) in variants.items(): + rendered = template.render( + pre_families=_pre_families(minimum, suffix), + aliases=_aliases(suffix), + families=_families(minimum, suffix) + extra_families(suffix), + model_config_args=model_config_args, + root=_sdk_root(suffix, with_version, model_config_args, with_extensions=variant == "write"), + enum_names=enum_names, + with_computed_field=with_computed_field, + ) + rendered = rendered.replace("__VARIANT__", suffix) + Path(f"{generated}/{variant}.py").write_text(rendered, encoding="utf-8") + + _write_sdk_generated_init(generated) + + execute_command(context=context, command=f'ruff format "{generated}"') + execute_command(context=context, command=f'ruff check --fix "{generated}"') def _jinja2_filter_inheritance(value: dict[str, Any], sync: bool = False) -> str: