The Graph Changed While the Approval Was Waiting
Treat approvals and snapshot checks as preparation, then use authorised, parameterised transactions with explicit conflict and retry behaviour.
14 sections · 4 questions · 1 coding challenge
The arrow keys move between sections too.
Connected Updates Needed a Transaction Boundary
BOTIE
The maintenance approval adds another boundary: a person approves a particular action against particular evidence. Rechecking that evidence at commit time prevents an old approval from silently becoming authority for a materially different state.
Sources for this history
An Approved Plan Is Now Based on an Old Record
Botie prepared a change for P8 when its revision was three. While a person reviewed that proposal, another technician updated P8 to revision four. Applying the old plan without checking would overwrite a decision made with newer information.
We need to separate preparing a change, approving it and committing it against current state. A snapshot check can reveal an obviously stale request, but only the database transaction can protect the final write from concurrent changes.
Invented maintenance records. Python exercises and workshop components expose the mechanism locally. Source-linked Cypher or SPARQL examples require the stated external database setup.
Step 1 of 1
Approval belongs to a particular proposed change
The stored asset has advanced beyond one proposal’s expected revision.
Nodes and properties (4)
- Proposal P7 · approved; expects revision 2
- P7 · current revision 2
- Proposal P8 · approved; expects revision 3
- P8 · current revision 4
- Proposal P7 to P7: PROPOSES_CHANGE
- Proposal P8 to P8: PROPOSES_CHANGE
Check approval against the graph snapshot
Explicit approval and a matching snapshot revision identify requests eligible for the next transaction stage; this preflight result is not evidence of a committed write.
Check approval against the graph snapshot
An approved request can become stale before it reaches the executor.
BOTIE
Result for these records
- 0P7
Follow the operations
1. Proposed requests and approvals
[ { "id": "P7", "expected_revision": 2, "approved": true }, { "id": "P8", "expected_revision": 3, "approved": true }, { "id": "P7", "expected_revision": 2, "approved": false } ]2. Keep explicitly approved requests
[ { "id": "P7", "expected_revision": 2, "approved": true }, { "id": "P8", "expected_revision": 3, "approved": true } ]3. Current node snapshot
Nodes and properties (2)
- P7 · Asset
- revision: 2
- P8 · Asset
- revision: 4
- No relationships in this result.
Inspect the node and relationship records
{ "nodes": [ { "id": "P7", "kind": "Asset", "revision": 2 }, { "id": "P8", "kind": "Asset", "revision": 4 } ], "edges": [] }- P7 · Asset
4. Check requests against a snapshot revision
[ { "id": "P7", "expected_revision": 2, "approved": true } ]5. Collect distinct identifiers
- 0P7
6. Result
- 0P7
These components operate on the supplied records in your browser. They do not run a graph database or interpret Cypher.
1. Keep the request separate from the stored entity
A proposal should identify its target, intended change, caller and the state it was based on. The approval should be tied to that proposal rather than a general reusable “yes.”
Our exercise deliberately selects eligible target IDs only. It does not apply status changes, validate arbitrary payloads or claim to perform an atomic update. Those responsibilities belong to the eventual authorised executor.
A useful preflight result says which requests need further execution checks.
2. Require explicit approval and the expected snapshot
Only boolean true counts as approval in the example. A string such as “true” is not the same typed value. Compare expected_revision with the target’s current snapshot revision, and exclude missing targets.
A stale request should return to review or be recalculated according to policy. Quietly changing expected_revision to the latest number would remove the protection the field was meant to provide.
Approval and freshness are independent conditions.
Inspect the revision mismatch
This program reports the two revisions rather than applying a change.
request={'expected_revision':3}
asset={'revision':4}
print(request['expected_revision']==asset['revision'])FalseThe approved request used older state. A new plan may need new approval.
3. The commit must enforce the concurrency rule
Between a browser preflight and a server write, another update can occur. Use a database-supported transaction and locking or conditional-update strategy whose isolation semantics have been checked for the deployed database.
A read in one transaction followed by an unconditional write in another is not optimistic concurrency control. Even a predicate and write in one statement need the engine’s documented concurrent-update semantics; do not assume a snapshot comparison creates a lock.
The workshop intentionally stops before commit and cannot establish transaction isolation.
Check yourself
Questions on Parts 1 to 3
Question 1 of 2
Should the preflight select it?
Use the information below.
A request is approved and expects revision 3. The current snapshot has revision 4.4. Parameterise the approved write and verify its result
The external example demonstrates the simpler unit: update one uniquely identified asset inside an application-managed transaction, then verify exactly one target was returned before committing. It does not implement the revision conflict policy by itself.
Use an authorised driver session, a parameterised reviewed query and explicit rollback when the matched target count violates the contract. Do not execute arbitrary Cypher generated by a user or model as the writer role.
Application validation, authorisation and concurrency control remain required around this query.
Run in your own environment
A parameterised write inside the executor transaction
This query must be run inside the transaction described in setup, not pasted into an unrestricted production console.
In a disposable database, create unique Asset.id values and install the uniqueness constraint from the preceding lesson.
Use a Neo4j driver transaction. Bind asset P7 and a validated status. Read the returned rows, require exactly one, and roll back on mismatch before commit.
For revision-sensitive requests, add a tested locking/conditional-update protocol at that same transaction boundary. This snippet does not supply that protocol.
MATCH (a:Asset {id:$asset})
SET a.status=$status
RETURN a.id AS updatedThe query changes one validated field. The transaction determines whether that change commits.
Official documentation. Documentation checked .
5. Retries must not repeat outside effects
A driver-managed transaction function may be invoked again after a transient failure. Sending an email, charging a service or issuing a physical maintenance action inside that retried function can repeat the effect even if the database transaction rolled back.
Keep external actions out of automatically retried database callbacks. Where an action must follow a commit, use a durable outbox or another explicit delivery design with idempotency keys and reconciliation. A database rollback cannot undo an email already sent.
Exactly-once business effects require more than calling MERGE or retrying a transaction.
6. MERGE and DELETE need their own identity and scope rules
MERGE matches or creates the supplied pattern. Match stable identity separately from mutable fields and back required uniqueness with constraints. A replayed logical request should have a stable idempotency identity if its outcome must be reconciled.
DELETE of a node with relationships and DETACH DELETE have different consequences. Broad deletion patterns can remove connected history. Preview the exact target identities and use least-privilege permissions; the lesson’s preflight does not authorise a deletion.
Read the write pattern as a description of everything it may affect.
7. Test stale, missing and ambiguously typed requests
The challenge checks explicit approval, known target and matching integer revision. It retains each eligible target ID once for this report. Real executors still need per-request identity, payload validation and conflict handling when several proposals target the same node.
That limited output makes the lesson’s claim testable: the records qualify for further execution checks under this snapshot. It never says those changes have been safely committed.
Keep the displayed result at the same level of authority as the mechanism that produced it.
Check yourself
Questions on Parts 1 to 7
Question 1 of 2
What can happen?
Use the information below.
A managed database transaction callback is retried. It also sends an email.Try it with Botie
Build with Botie
Write and run Python