- Je onderscheidt een modelvoorstel van de hostactor, het toolregister en een vertrouwd goedkeuringsbesluit.
- Je test gewijzigde argumenten, verouderde versies en herhaalde call-IDs zonder echte externe acties.
Je assistent stelt voor een reparatieticket af te sluiten. Je hebt juist dát ticket en die status bekeken. Als de argumenten daarna veranderen, mag het oude akkoord geen andere handeling goedkeuren.
We oefenen met een Python-dictionary met twee fictieve tickets. De host, de applicatie rond het model, beheert identiteit, toestemmingen en uitvoering. Modelvoorstellen en toolbeschrijvingen vullen dat register niet zelf; deze oefening verstuurt niets naar een externe dienst.
1. Benoem de handeling vóór je haar beoordeelt
Een voorstel bevat call_id, actor, name, version en arguments. De geclaimde actor is vergelijkbaar met een naam op een formulier: de host moet haar nog vergelijken met de afzonderlijk vertrouwde sessieactor. De naam set_ticket is de enige toegestane schrijfhandeling.
p = {
'call_id': 'c1', 'actor': 'learner',
'name': 'set_ticket', 'version': 0,
'arguments': {
'ticket': 'lamp',
'status': 'done'}
}
clean = validate_ticket_proposal(p)
p['arguments']['status'] = 'open'
print(clean['arguments']['status'])
p = clean
# done
Schrijf validate_ticket_proposal volgens het exacte contract. Alleen open en done zijn geldige statussen; identificaties zijn begrensde ASCII-strings en version is een gewone integer. Een syntactisch geldige onbekende toolnaam blijft hier geldig: pas de dispatcher beoordeelt beschikbaarheid.
De gekopieerde arguments bewaren de beoordeelde inhoud. Een extra veld dat beweert dat het voorstel al goedgekeurd is, maakt het voorstel juist ongeldig.
012. Bind een akkoord aan dezelfde inhoud
Denk aan een verzegelde kopie van het volledige formulier. Een vingerafdruk (fingerprint) helpt inhoud vergelijken, maar iedereen kan die hash berekenen. Alleen een afzonderlijk hostbesluit maakt een vingerafdruk onderdeel van het goedkeuringsregister.
Noem het voorstel p en de vaste JSON-weergave C(p). Sorteer alle sleutels, gebruik ASCII-escapes en verwijder de optionele spaties met de opgegeven separators; H is de SHA-256-hash van die bytes:
encoded = json.dumps(
p, sort_keys=True,
ensure_ascii=True,
separators=(',', ':'))
print(','.join(json.loads(encoded)))
print(len(proposal_fingerprint(p)))
other = dict(reversed(list(p.items())))
print(proposal_fingerprint(p) ==
proposal_fingerprint(other))
# actor,arguments,call_id,name,version
# 64
# True
De andere invoervolgorde verandert de inhoud niet. Neem ook call-ID, actor, tool, versie en beide argumenten mee; alleen de ticketnaam hashen zou een gewijzigde status niet onderscheiden. Schrijf proposal_fingerprint en laat het goedkeuringsregister buiten de policy.
Je speelt de host. Keur een voorstel goed, verander daarna één veld en probeer de actie. De vingerafdruk wordt echt met SHA-256 berekend.
learner · c1 · versie 0
lamp → doneVingerafdrukBerekenen…Geen passende goedkeuringEen hash is geen toestemming. Alleen jouw knopbesluit vult hier het aparte register. Herhalen van dezelfde geslaagde call leest het bewaarde resultaat en verandert niets opnieuw.
3. Voer precies één beoordeelde wijziging uit
Aanvankelijk staan lamp en sensor open op versie 0. Voor een nieuwe call controleert dispatch_ticket eerst hostinvoer, actor en toolnaam. Daarna volgen herhaling of conflict, actuele versie, toestemming, bestaand ticket en logruimte.
De volgende code speelt expliciet de host die het voorstel goedkeurt. Met dat besluit wordt uitsluitend lamp done; sensor blijft open en de globale versie wordt 1.
approved = {proposal_fingerprint(p)}
state = {'version': 0, 'tickets': {
'lamp': 'open', 'sensor': 'open'}}
log = {}
r = dispatch_ticket(
p, 'learner', approved, state, log)
print(r['status'], state['version'])
print(state['tickets']['lamp'],
state['tickets']['sensor'])
# ok 1
# done open
Bewaar bij succes de vingerafdruk en een gekopieerd resultaat in completed. Een weigering verandert geen ticket, versie of voltooid-log. Onze globale versie kan ook door een wijziging aan een ander ticket verouderen; dat is een bewuste vereenvoudiging.
4. Herhaal het resultaat zonder het effect
Idempotentie betekent hier dat dezelfde geslaagde call geen tweede wijziging uitvoert. De identieke call krijgt het opgeslagen resultaat met replayed=True, ook zonder opnieuw goedkeuring te gebruiken. De actorcontrole blijft wél vóór deze replay staan.
r = dispatch_ticket(
p, 'learner', set(), state, log)
print(r['replayed'], state['version'])
r['version'] = 900
print(log['c1']['result']['version'])
r = dispatch_ticket(
p, 'other', set(), state, log)
print(r['status'])
# True 1
# 1
# forbidden
De teruggegeven kopie beschermt het opgeslagen resultaat. Een nieuwe uitvoering met dezelfde call-ID maar andere inhoud wordt geweigerd; een nieuwe call-ID met de oude versie eveneens:
changed = {**p, 'arguments': {
'ticket': 'lamp', 'status': 'open'}}
r = dispatch_ticket(
changed, 'learner', approved,
state, log)
print(r['status'])
new = {**p, 'call_id': 'c2'}
r = dispatch_ticket(
new, 'learner', approved,
state, log)
print(r['status'], state['version'])
# call-id-conflict
# stale-version 1
Schrijf dispatch_ticket met deze controlevolgorde en de overige begrenzingen uit de opdracht. Het interne completed-log is vertrouwd, beperkt en alleen in geheugen aanwezig. Crashbestendige opslag, transacties en gelijktijdige uitvoering zijn daarmee nog niet opgelost.
03Wat neem je mee?
- Vormcontrole bewijst geen identiteit of toestemming.
- Een hash bindt het bekeken voorstel, maar verleent zelf geen bevoegdheid.
- Replay herhaalt een resultaat; alleen een nieuwe geslaagde call verandert toestand.
Hierna plannen we afhankelijkheden en beslissen we welke mislukte taken veilig opnieuw mogen.