#!/usr/bin/env python3
import json, sys

def classify(record):
    if not isinstance(record, dict) or record.get('contract_version') != 'inbox-ack-v1':
        return {'decision':'REFUSE','reason':'unknown_or_malformed_contract'}
    mode = record.get('mode')
    offered = record.get('offered_cursor')
    sent = record.get('sent_cursor')
    processed = record.get('processed_ids')
    if mode not in ('singleton','batch') or not isinstance(offered, int) or not isinstance(sent, int):
        return {'decision':'REFUSE','reason':'missing_cursor_fields'}
    if mode == 'singleton':
        if not isinstance(processed, list) or len(processed) != 1 or sent != offered:
            return {'decision':'REFUSE','reason':'singleton_must_bind_exact_read'}
        if processed[0] != offered:
            return {'decision':'REFUSE','reason':'processed_id_not_offered'}
        return {'decision':'PASS','reason':'singleton_exact_binding'}
    # Batch cannot infer provenance from a componentwise minimum. It needs a server-bound token.
    if not isinstance(processed, list) or not processed or any(not isinstance(x,int) for x in processed):
        return {'decision':'REFUSE','reason':'batch_requires_processed_set'}
    if record.get('server_token') is None:
        return {'decision':'UNKNOWN','reason':'batch_set_not_server_bound'}
    if record.get('server_token') != record.get('token_for_processed_set'):
        return {'decision':'REFUSE','reason':'token_set_mismatch'}
    if sent != min(processed):
        return {'decision':'REFUSE','reason':'sent_cursor_not_derived_from_bound_set'}
    return {'decision':'PASS','reason':'server_bound_processed_set'}

def main():
    for line in sys.stdin:
        if line.strip(): print(json.dumps(classify(json.loads(line)), sort_keys=True))
if __name__ == '__main__': main()
