{% extends "base.html" %} {% block title %}Pup ID Β· {{ app_name }}{% endblock %} {% block body %} {% macro arrive_form(label) %}
{% endmacro %}A Pup ID is a key that is you: it signs the papers for your badges, it is who a Handler collars and who a pack knows, and it moves with you to a new box.
π΄ {{ make_warning }}
{% if hosted %}β οΈ {{ hosted_warning }}
{% endif %}Your pup already lives somewhere? Bring it here with your pup.json (your key sealed under your passphrase, your papers, your travelling kit and photos) β or with just your recovery sheet, which brings the key alone. Only a box with no Pup ID can arrive.
{{ arrive_form('Arrive') }}{{ banner }}
{{ headline }}
{% if soc and soc.paused %}π Paused β everything you granted is suspended. Resume on the Consent page (only there, on the web).
{% endif %} {% if soc and soc.packs %}Packs: {% for p in soc.packs %}{{ p.name or p.uid }}{{ ', ' if not loop.last }}{% endfor %}
{% endif %}Read the fingerprint aloud to compare it β it is what a Handler checks against their screen.
{% for w in ident.perms %}β {{ w }}
{% endfor %}π Papers ({{ n_papers }}) π§³ What travels ({{ n_carry }} photos) β¬ pup.json
{% if ident.state == 'ready' or ident.moving_at %}{% if left_behind %}β pup.json carries no pack β this box is still in {{ left_behind|length }} pack{{ 's' if left_behind|length != 1 }}. Read this before you move by pup.json.
{% endif %} {% else %}{% with moving=ident.moving_at, retired=(ident.state == 'moved') %}{% include "_pupjson_packs.html" %}{% endwith %}{% endif %} {% if n_unpapered and ident.state == 'ready' and not ident.moving_at %}{{ n_unpapered }} badge(s) get their paper on the next tick.
{% endif %}Your name was given by {{ pup_profile.name_given_by.name }} with their collar.
{% endif %}β οΈ {{ backup_warning }}
{% if hosted %}β οΈ {{ hosted_warning }}
{% endif %} {% if ident.state == 'locked' %} {% elif ident.state == 'missing' %}This box knows your Pup ID but has no key for it. Bring the key home from your pup.json or your recovery sheet β only this pup's own key comes back, with any papers this box lacks. The game on this box β its kit, its photos, its profile β stays exactly as it is.
{{ arrive_form('Bring my key home') }} {% elif ident.state == 'mismatch' %}The key file here is not the Pup ID this box says it is. Nothing is signed. Put the right
identity/pup.key back β or restore your bundle onto a fresh box (a restore never
goes onto a box that holds a key file).
A passphrase seals a copy of your key (at least {{ min_pass }} characters). The sealed copy is what pup.json and the backup zip carry β never the plain key.
{% if ident.sealed %}β οΈ Changing the passphrase re-seals the copy on this box only. A pup.json or backup zip you already made still opens with the old passphrase, and a recovery sheet needs none. If one of those leaked, your key leaked β there is no key rotation yet.
{% endif %} {% endif %} {% if ident.state in ('ready', 'locked') %}{% if ident.seal_at_rest %}On: only the sealed file is on disk, and the key is held in memory until this app restarts β then it waits for your passphrase.{% else %}Off: the plain key file is on this box's disk (0600).{% endif %}
{% if ident.sealed or ident.seal_at_rest %} {% else %}Seal it with a passphrase first (above) β seal-at-rest deletes the plain file, so the sealed copy has to be one you can open.
{% endif %} {% if ident.sealed %}Your key written out by hand β for the day this box and your pup.json are both gone. Anyone holding it holds your pup. It needs your passphrase to print.
{% endif %} {% endif %}π΄ Your key is on another box you did not move to{% if ident.retired.box_fp %} ({{ ident.retired.box_fp }}){% endif %}. Your key was copied. This box signs nothing.
{% else %}This box is retired: {{ 'another box holds this pup' if ident.retired and ident.retired.reason == 'rehomed' else 'you moved your pup away' }}{% if ident.retired and ident.retired.box_fp %} (fingerprint {{ ident.retired.box_fp }}){% endif %}{% if ident.retired and ident.retired.at %}, since {{ ident.retired.at|local('%Y-%m-%d') }}{% endif %}. The sealed copy of the key stays here.
{% endif %}This pup is packing to move: packs, your AI and papers are stopped (since {{ ident.moving_at|local('%Y-%m-%d %H:%M') }}). Your own game β the lock, the emergency key, everything β keeps working.
python -m latch.restore), or download pup.json and arrive there.
pup.json carries no pack: the game's bundle keeps your packs, but by pup.json each den still
holds your membership through THIS box and refuses a new join until it is gone β so leave your
packs before you prepare (or ask their operators to remove you), then join again with fresh tags.
{% with moving=True %}{% include "_pupjson_packs.html" %}{% endwith %}Retiring asks for your passphrase: it proves the sealed copy the new box relies on actually opens before this box deletes its plain key.
{% elif ident.state == 'ready' %}Moving is explicit. Preparing stops everything that talks to anyone else β packs, your AI, papers β so nothing is signed twice from two boxes. Cancel any time.
{% with moving=False %}{% include "_pupjson_packs.html" %}{% endwith %} {% else %}Unlock your key first.
{% endif %} {% if box_fp %}This box's own fingerprint: {{ box_fp }}
{% endif %}