Documentos de Asset Core

Documentación del motor de estado mundial determinista y referencias de API.

Documentos de Decision Gate

Cadena de Custodia del Laboratorio: Reconciliación Bajo Auditoría

Siembra un rack de laboratorio a partir de un manifiesto declarativo, activa un fallo de pre-vuelo determinista y reconcilia a través de una colocación sin cuadrícula.

Si está leyendo esto sin contexto, este es un flujo de trabajo de reconciliación de laboratorio: seguimiento de custodia determinista, detección de conflictos en pre-vuelo y un movimiento de remediación verificado. Cada cambio de custodia es un compromiso auditable, por lo que los investigadores pueden reproducir, verificar y explicar lo que sucedió sin inferencias.

El Problema: Interrupciones de Custodia Bajo Auditoría

Las cadenas de custodia clínicas fallan cuando el inventario físico, los registros de escáner y los sistemas operativos no coinciden. Cuando una colocación falla, necesitas evidencia determinista de que el sistema detectó el conflicto antes de cualquier cambio de estado.

Este escenario demuestra un patrón de calidad de producción: un fallo forzado en la prevalidación, una remediación guiada por lectura y un rastro de auditoría reproducible.

Por qué esto es importante

Los laboratorios regulados necesitan una custodia trazable y una remediación determinista cuando aparecen conflictos. Asset Core hace que la custodia sea autoritativa al tratar cada cambio como una transacción determinista y reproducible.

Modelo del sistema

  • Estantes: contenedores de rejilla que imponen reglas de ocupación por laboratorio.
  • Bahía del escáner: contenedor de ranura para verificaciones de custodia en vuelo.
  • Grupos de reactivos: contenedores de equilibrio con cuantización de punto fijo.
  • Clases y formas: registradas para que las reglas de colocación se mantengan consistentes.
  • Unidades: celdas (fixed_point_scale=1000).

Flujo de trabajo en un vistazo

  1. Configuración - Registrar clases y crear contenedores de laboratorio.
  2. Semilla - Sembrar tubos iniciales y balances.
  3. Preflight Bloqueado - El preflight falla al colocar en una celda ocupada.
  4. Grid Free - Seleccione el siguiente ancla de cuadrícula disponible.
  5. Conciliar - Mueva el tubo del escáner a un ancla libre verificada.
  6. Verificar la cuadrícula - Confirmar la colocación reconciliada en la proyección de lectura.

Cómo leer la guía paso a paso

  • Los pasos de una sola operación utilizan ayudantes de acción para mejorar la legibilidad.
  • Los pasos de múltiples operaciones permanecen como llamadas de confirmación para preservar la atomicidad.
  • Rust HTTP siempre utiliza el endpoint de commit con una o más operaciones.

Artefactos de escenario

Última verificación

  • ID de ejecución: 2026-01-19T18-52-12Z-lab_chain_of_custody
  • Estado: Aprobado
  • Hash raíz de integridad: efcb02df86d0dd2bb9ffd14d8a7403e52bd06b7c5f56fb4fc4f04703dc9f542f
  • Checks:
    • {‘kind’: ‘invariantes estructurales’}: aprobado
    • {‘kind’: ‘world_seq_monotonic’}: aprobado
    • {‘kind’: ‘global_seq_monotonic’}: aprobado
    • {'kind': 'owner_scope', 'allowed_owners': [1, 2]}: aprobado
    • {'kind': 'conservación_cantidad', 'container_id': 4, 'class_id': 2, 'stack_key': None, 'expected': 250000}: aprobado
    • {‘kind’: ‘replay_idempotent’}: pasado
    • {'kind': 'commit_log_integrity', 'expected_chain_hash': 'aa3301e51d32150b30281395b68f6df815eaff8416260789832c06259e84a30c', 'expected_prev_chain_hash': 'c387f5f5a7fec5ce1223fa9dcbc8d564a5221e50a7afa1fba498c625d693f064', 'expected_start_offset': 1, 'expected_end_offset': 3, 'expected_entry_count': 3, 'expected_last_global_seq': 38}: aprobado
    • {‘kind’: ‘runpack_integrity’}: aprobado

Descargas de Runpack

Instantánea del escenario

{
  "scenario_id": "lab_chain_of_custody",
  "version": "0.1.0",
  "namespace_id": 2,
  "containers": {
    "rack_lab_a": "container-32001",
    "rack_lab_b": "container-32002",
    "reagent_pool_lab_a": "container-32004",
    "reagent_pool_lab_b": "container-32005",
    "scanner_bay_lab_a": "container-32003"
  },
  "classes": {
    "reagent_buffer": "class-42002",
    "sample_tube": "class-42001"
  },
  "units": {
    "fixed_point_scale": 1000,
    "length_unit": "cells",
    "time_unit": "ms"
  }
}

Fallo de rama

POSITION_OCCUPIED -> grid_free -> reconciliar movimiento

Registro de auditoría (opcional)

La transcripción captura los pares de solicitud y respuesta exactos emitidos durante la ejecución. Aquí hay un extracto compacto que puedes usar para validar el determinismo y la propagación de metadatos:

{
  "kind": "commit",
  "name": "setup",
  "request": {
    "path": "/v1/write/namespaces/2/commit",
    "idempotency_key": "lab-chain-setup",
    "actor_id": "lab-chain",
    "metadata": {
      "phase": "setup",
      "scenario": "lab_chain_of_custody"
    },
    "operations": [
      "RegisterClass",
      "RegisterClassShape",
      "RegisterClass",
      "CreateContainer",
      "CreateContainer",
      "CreateContainer",
      "CreateContainer",
      "CreateContainer"
    ]
  },
  "response": {
    "commit_id": "00000000000000000000000000000003",
    "world_seq_start": 1,
    "world_seq_end": 8,
    "event_count": 8
  }
}

Nota de pre-vuelo

  • Las llamadas a la acción envían confirmaciones por defecto.
  • Establezca ActionOptions(preflight=True) o llame a assetcore_commit_preflight para validación.

Configuración del SDK

from assetcore_sdk import AssetCoreClient
from assetcore_sdk.actions import ActionOptions
from assetcore_sdk.operations import (
    AddFungible,
    AddInstance,
    CreateContainer,
    MoveInstance,
    RegisterClass,
    RegisterClassShape,
)

# Snippets assume an async context (e.g., inside async def main()).
write_client = AssetCoreClient(
    base_url="http://localhost:8080",
    api_key="WRITE_API_KEY",
)
read_client = AssetCoreClient(
    base_url="http://localhost:8081",
    api_key="READ_API_KEY",
)

Guía

Paso 1: Configuración

Registrar clases y crear contenedores de laboratorio.

Operaciones

  • RegisterClass (x2) - Registra una definición de clase para que las operaciones futuras puedan hacer referencia a ella.
  • RegisterClassShape - Registra una huella de forma de cuadrícula para una clase o variante de clase.
  • CreateContainer (x5) - Crea un contenedor (región de memoria estructurada) con el tipo solicitado.
await write_client.commit_operations(
    [
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1},
                "class_id": "class-42001",
                "flags": 0,
                "name": "sample_tube",
            },
        ),
        RegisterClassShape(
            request={"class_id": "class-42001", "shape": {"height": 1, "width": 1}},
        ),
        RegisterClass(
            request={
                "behavior": {"balance_scale": 1000},
                "class_id": "class-42002",
                "flags": 0,
                "name": "reagent_buffer",
            },
        ),
        CreateContainer(
            client_tag="rack_lab_a",
            external_id="container-32001",
            kind={"capacity": 96, "grid_width": 12, "type": "grid"},
            owner_external_id="owner-101",
            policies=None,
        ),
        CreateContainer(
            client_tag="rack_lab_b",
            external_id="container-32002",
            kind={"capacity": 96, "grid_width": 12, "type": "grid"},
            owner_external_id="owner-102",
            policies=None,
        ),
        CreateContainer(
            client_tag="scanner_bay_lab_a",
            external_id="container-32003",
            kind={"count": 2, "type": "slots"},
            owner_external_id="owner-101",
            policies=None,
        ),
        CreateContainer(
            client_tag="reagent_pool_lab_a",
            external_id="container-32004",
            kind={"quantization_inv": 1000, "type": "balance"},
            owner_external_id="owner-101",
            policies=None,
        ),
        CreateContainer(
            client_tag="reagent_pool_lab_b",
            external_id="container-32005",
            kind={"quantization_inv": 1000, "type": "balance"},
            owner_external_id="owner-102",
            policies=None,
        ),
    ],
    namespace_id=2,
    idempotency_key="lab-chain-setup",
    actor_id="lab-chain",
    metadata={"phase": "setup", "scenario": "lab_chain_of_custody"},
)

Paso 2: Semilla

Siembra tubos iniciales y balances.

Operaciones

  • AddInstance (x25) - Crea una nueva instancia y la coloca en una ubicación objetivo.
  • AddFungible (x2) - Añade una cantidad fungible a un saldo o a una celda de cuadrícula explícita.
await write_client.commit_operations(
    [
        AddInstance(
            class_id="class-42001",
            client_tag="tube-001",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 1,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-002",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 2,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-003",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 3,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-004",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 4,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-005",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 5,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-006",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 6,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-007",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 7,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-008",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 8,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-009",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 9,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-010",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 10,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-011",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 11,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-012",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 12,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-013",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 13,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-014",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 14,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-015",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 15,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-016",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 16,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-017",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 17,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-018",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 18,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-019",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 19,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-020",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 20,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-021",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 21,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-022",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 22,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-023",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 23,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="tube-024",
            key=None,
            location={
                "container_id": "container-32001",
                "kind": "grid_cell",
                "position": 24,
                "rotation": None,
            },
        ),
        AddInstance(
            class_id="class-42001",
            client_tag="scanner-tube",
            key=None,
            location={
                "container_id": "container-32003",
                "kind": "slot",
                "slot_index": 1,
            },
        ),
        AddFungible(
            class_id="class-42002",
            client_tag="reagent-a",
            key=None,
            location={"container_id": "container-32004", "kind": "balance"},
            quantity="250",
        ),
        AddFungible(
            class_id="class-42002",
            client_tag="reagent-b",
            key=None,
            location={"container_id": "container-32005", "kind": "balance"},
            quantity="125",
        ),
    ],
    namespace_id=2,
    idempotency_key="lab-chain-seed",
    actor_id="lab-chain",
    metadata={"phase": "seed", "scenario": "lab_chain_of_custody"},
)

Paso 3: Preflight Bloqueado (Error esperado)

La verificación previa falla al colocar en una celda ocupada.

Operaciones

  • MoveInstance - Mueve una instancia existente a una nueva ubicación.

Error esperado

{
  "code": "POSITION_OCCUPIED",
  "detail": "Position 6 in container 1 is occupied by item at anchor 6",
  "hint": "Select an unoccupied position or move the existing item first.",
  "retryable": false,
  "status": 409,
  "title": "ConflictError",
  "type": "urn:assetcore:error:POSITION_OCCUPIED"
}
await write_client.actions.move_instance(
    instance="inst-25",
    to={
        "container_id": "container-32001",
        "kind": "grid_cell",
        "position": 6,
        "rotation": None,
    },
    options=ActionOptions(
        namespace_id=2,
        idempotency_key="lab-chain-blocked",
        actor_id="lab-chain",
        metadata={"phase": "exception", "scenario": "lab_chain_of_custody"},
    ),
)

Paso 4: Sin Rejilla

Selecciona el siguiente ancla de cuadrícula disponible.

await read_client.get_container_grid_free(
    container_id="container-32001",
    namespace_id=2,
    height=1,
    width=1,
)

Paso 5: Conciliar

Mueva el tubo del escáner a un ancla libre verificada.

Operaciones

  • MoveInstance - Mueve una instancia existente a una nueva ubicación.
await write_client.actions.move_instance(
    instance="inst-25",
    to={
        "container_id": "container-32001",
        "kind": "grid_cell",
        "position": 25,
        "rotation": None,
    },
    options=ActionOptions(
        namespace_id=2,
        idempotency_key="lab-chain-reconcile",
        actor_id="lab-chain",
        metadata={
            "phase": "reconcile",
            "scenario": "lab_chain_of_custody",
            "target_anchor": 25,
        },
    ),
)

Paso 6: Verificar la cuadrícula

Confirme la colocación reconciliada en la proyección de lectura.

await read_client.get_container_grid(
    container_id="container-32001",
    namespace_id=2,
)