Documentació d'Asset Core

Documentació del motor d'estat del món determinista i referències de l'API.

Decision Gate docs

Lab Cadena de Custòdia: Reconciliació Sota Auditoria

Sembrar un rack de laboratori a partir d’un manifest declaratiu, desencadenar un error de prevol determinista i reconciliar mitjançant una col·locació sense graella.

Si estàs llegint això en fred, aquest és un flux de treball de reconciliació de laboratori: seguiment de custòdia determinista, detecció de conflictes abans del vol i un moviment de remediació verificat. Cada canvi de custòdia és un compromís auditable perquè els investigadors puguin reproduir, verificar i explicar el que ha passat sense inferència.

El Problema: Pauses de Custòdia Sota Auditoria

Les cadenes de custòdia clíniques fallen quan l’inventari físic, els registres d’escàner i els sistemes operatius no coincideixen. Quan una col·locació falla, necessiteu proves deterministes que el sistema va detectar el conflicte abans de qualsevol canvi d’estat.

Aquest escenari demostra un patró de qualitat de producció: una fallada de prevol forçada, una remediació guiada per lectura i un rastre d’auditoria reproduïble.

Per què això és important

Els laboratoris regulats necessiten custòdia traçable i remediació determinista quan apareixen conflictes. Asset Core fa que la custòdia sigui autoritativa tractant cada canvi com una transacció determinista i reproduïble.

Model de sistema

  • Racks: contenidors de graella que imposen regles d’ocupació per laboratori.
  • Scanner bay: contenidor d’eslots per a comprovacions de custòdia en vol.
  • Grups de reactius: contenidors de balanç amb quantització de punt fix.
  • Classes i formes: registrades perquè les regles de col·locació es mantinguin coherents.
  • Unitats: cel·les (fixed_point_scale=1000).

Visió general del flux de treball

  1. Configuració - Registrar classes i crear contenidors de laboratori.
  2. Semilla - Sembrar tubs inicials i equilibris.
  3. Preflight Bloquejat - El preflight falla quan es col·loca en una cel·la ocupada.
  4. Reixeta lliure - Seleccioneu l’ancoratge de reixeta disponible següent.
  5. Reconciliar - Mou el tub de l’escàner a un ancoratge lliure verificat.
  6. Verificar la reixeta - Confirmar la col·locació reconciliada en la projecció de lectura.

Com llegir la guia pas a pas

  • Els passos d’una sola operació utilitzen helpers d’acció per a la llegibilitat.
  • Els passos de múltiples operacions es mantenen com a crides de compromís per preservar l’atomicitat.
  • Rust HTTP sempre utilitza el punt final de commit amb una o més operacions.

Artifacts de l’escenari

Última verificació

  • ID d’execució: 2026-01-19T18-52-12Z-lab_chain_of_custody
  • Status: Aprovat
  • Hash arrel d’integritat: efcb02df86d0dd2bb9ffd14d8a7403e52bd06b7c5f56fb4fc4f04703dc9f542f
  • Checks:
    • {‘kind’: ‘invariants estructurals’}: aprovat
    • {‘kind’: ‘world_seq_monotonic’}: aprovat
    • {‘kind’: ‘global_seq_monotonic’}: aprovat
    • {'kind': 'owner_scope', 'allowed_owners': [1, 2]}: aprovat
    • {'kind': 'quantity_conservation', 'container_id': 4, 'class_id': 2, 'stack_key': None, 'expected': 250000}: aprovat
    • {‘kind’: ‘replay_idempotent’}: passat
    • {'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}: aprovat
    • {‘kind’: ‘runpack_integrity’}: aprovat

Descarregues de Runpack

Instantània de l’escenari

{
  "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"
  }
}

Failure branch

POSITION_OCCUPIED -> grid_free -> reconciliar moviment

Audit trail (opcional)

La transcripció captura les parelles de sol·licitud i resposta exactes emeses durant l’execució. Aquí hi ha un extracte compacte que podeu utilitzar per validar el determinisme i la propagació de metadades:

{
  "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 prevol

  • Per defecte, les accions criden a enviar compromisos.
  • Estableix ActionOptions(preflight=True) o crida assetcore_commit_preflight per a la validació.

SDK configuració

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",
)

Guia

Pas 1: Configuració

Registreu classes i creeu contenidors de laboratori.

Operacions

  • RegisterClass (x2) - Registra una definició de classe perquè les operacions futures la puguin referenciar.
  • RegisterClassShape - Registra una empremta de forma de graella per a una classe o variant de classe.
  • CreateContainer (x5) - Crea un contenidor (regió de memòria estructurada) amb el tipus sol·licitat.
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"},
)

Pas 2: Sembrar

Sembla que hi ha un error en la sol·licitud. No hi ha text per traduir. Si us plau, proporcioneu el text que voleu que tradueixi al català.

Operacions

  • AddInstance (x25) - Crea una nova instància i la col·loca en una ubicació objectiu.
  • AddFungible (x2) - Afegeix una quantitat fungible a un saldo o a una cel·la de la graella 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"},
)

Pas 3: Preflight Bloquejat (Error esperat)

El preflight falla quan s’intenta col·locar en una cel·la ocupada.

Operacions

  • MoveInstance - Mou una instància existent a una nova ubicació.

Error esperat

{
  "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"},
    ),
)

Pas 4: Reixeta Lliure

Seleccioneu l’ancoratge de graella disponible següent.

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

Pas 5: Reconciliar

Mou el tub d’escàner a un ancoratge lliure verificat.

Operacions

  • MoveInstance - Mou una instància existent a una nova ubicació.
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,
        },
    ),
)

Pas 6: Verificar la Xarxa

Confirmeu la col·locació reconciliada en la projecció de lectura.

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