Encumbrances
An encumbrance is a non-binding reservation of a future operation. It records that a transfer, movement, or attribute change is planned without writing it to the history tables described in Database Architecture.
Protocols
A Protocol is a named, experiment-scoped group of encumbrances. The examples use a new database with a lab holding a bench and two plates, and one experiment:
julia> path = joinpath(mktempdir(), "lab.db");
julia> create_db(path);
julia> connect_SQLite(path)
julia> lab = generate_location(loc"Lab", "Lab");
julia> bench = generate_location(loc"Bench", "Bench");
julia> plate1 = generate_location(loc"WP96", "Plate 1");
julia> plate2 = generate_location(loc"WP96", "Plate 2");
julia> for x in (bench, plate1, plate2); upload(move_into!, lab, x); end
julia> exp_id = upload_experiment("demo experiment", "docs")
1
julia> p_id = upload_protocol(exp_id, "move plates to bench")
1The pair (ExperimentID, Name) is unique, so a protocol has a stable identity within its experiment. Each protocol also has an enforcement flag that is stamped with a ledger entry, so enforcement can be switched on and off over time.
Encumbering an operation
An encumbrance has no type of its own. It is an integer Encumbrances.ID that ties a ProtocolID to one row in an operation-specific table: EncumberedTransfers, EncumberedMovements, EncumberedEnvironments, EncumberedLocks, or EncumberedActivity.
encumber takes protocol_id, fun, args.... It runs the CHESSCore operation immediately in memory and then records the reservation in the matching table. The operation is not deferred or simulated:
julia> enc_move1 = encumber(p_id, move_into!, bench, plate1)
1
julia> print(parent(plate1)) # moved in memory already
Bench
julia> print(parent(reconstruct_location(CHESSCore.location_id(plate1)))) # not in the database
LabNon-binding refers to the database only: nothing is written to Movements, Transfers, EnvironmentAttributes, or the other history tables. The in-memory objects change immediately, and nothing reverses that change automatically. A movement encumbrance that passes a trailing true to lock the location does lock it in memory:
julia> enc_move2 = encumber(p_id, move_into!, bench, plate2, true)
2
julia> is_locked(plate2)
true
julia> unlock!(plate2); # reversing the in-memory lock is manualCompleting an encumbrance
Performing the real operation later does not mark the encumbrance complete. A separate call links the two. Here the real move is uploaded from a reconstruction, because plate1 in memory has already been moved:
julia> move_id = upload(move_into!, bench, reconstruct_location(CHESSCore.location_id(plate1)));
julia> CHESSDatabase.upload_encumbrance_completion(enc_move1, move_id)Nothing ties the real operation to the encumbrance automatically. An encumbrance can be marked complete without the operation having been performed, and the operation can be performed without completing the encumbrance. Encumbrances record intent, and the caller links them to the ledger.
Status queries
julia> CHESSDatabase.get_all_encumbrances(p_id)
2-element Vector{Int64}:
1
2
julia> CHESSDatabase.get_encumbrance_status(p_id)
2×4 DataFrame
Row │ EncumbranceID Operation LedgerID IsComplete
│ Int64 String Integer? Bool?
─────┼────────────────────────────────────────────────
1 │ 1 Movement 6 true
2 │ 2 Movement missing falseget_all_encumbrances(protocol_id)lists every encumbrance ID in a protocol.get_encumbrance_completion(encumbrance_ids)reports whether each is linked to a ledger entry.get_all_protocolsandget_protocol_statussummarize at the protocol level, giving the number of completed encumbrances against the total.
get_last_protocol_id returns the ID of the most recently created protocol of an experiment, and get_last_encumbrance_id returns the ID of the most recently created encumbrance of a protocol:
julia> get_last_protocol_id(exp_id)
1
julia> get_last_encumbrance_id(p_id)
2Caching encumbered state
encumber_cache stores a snapshot of the state of a location under an encumbrance. It is the encumbrance counterpart of cache (see Caching & Repair) and is used by reconstructions that include planned operations. It takes the encumbrance ID and the location:
julia> encumber_cache(enc_move2, plate2)Instrument Interfaces describes how the capability check and the recording of the instrument are divided between the two packages.