{% extends 'generic/object.html' %} {% load helpers %} {% load i18n %} {% block content %}
{% trans "Change" %}
{% trans "Reference" %} {{ object.ref|placeholder }}
{% trans "Source" %} {{ object.source|linkify }}
{% trans "State" %} {% badge object.get_state_display bg_color=object.get_state_color %}
{% trans "Verdict" %} {% if object.verdict %} {% badge object.get_verdict_display bg_color=object.get_verdict_color %} {% else %} {% trans "Not yet verified" %} {% endif %}
{% trans "Requester" %} {{ object.requester|placeholder }}
{% trans "Priority" %} {{ object.priority }}
{% trans "Summary" %} {{ object.summary|placeholder }}
{% if perms.forward_netbox.change_forwardchange %} {% if transitions %}

{% trans "Move this change on:" %}

{% for move in transitions %}
{% csrf_token %}
{% endfor %} {% for move in transitions %} {% if not move.allowed and move.blockers %}
{{ move.target }}: {{ move.blockers|join:" " }}
{% endif %} {% endfor %} {% endif %} {% if object.state == 'verified-proceed' %}
{% csrf_token %}
{% endif %} {% endif %}
{% trans "Snapshots" %}
{# Pinned as literal ids, never selectors: verifying against a #} {# re-resolved snapshot would judge the change against a different #} {# network than the one it was approved against. #}
{% trans "Baseline (before)" %} {{ object.before_snapshot_id|placeholder }}
{% trans "Post-change (after)" %} {{ object.after_snapshot_id|placeholder }}
{% trans "Branch" %} {{ object.branch_name|placeholder }}
{% trans "Applied" %}

{% trans "Attested, not measured. Nothing here can observe the network being changed, so this is a human claim about when it happened." %}

{% trans "When" %} {{ object.applied_at|placeholder }}
{% trans "Attested by" %} {{ object.applied_by|placeholder }}
{% trans "Reference" %} {{ object.applied_ref|placeholder }}
{% if perms.forward_netbox.change_forwardchange and object.state == 'approved' %}
{% csrf_token %}
{% endif %}
{% trans "Criteria" %}

{% trans "Each criterion is an NQE query and what to expect of it. It is bound to a committed query version before anything is measured, and cannot change once evidence exists." %}

{% if criteria %} {% for criterion in criteria %} {% endfor %}
{{ criterion.name }}{% if not criterion.blocking %} {% trans "advisory" %}{% endif %} {{ criterion.get_expectation_display }} {% if criterion.is_bound %} {% trans "bound" %} {% else %} {% trans "not bound" %} {% endif %} {% if perms.forward_netbox.change_forwardchangecriterion %}
{% csrf_token %}
{% trans "Edit" %} {% endif %} {% if perms.forward_netbox.delete_forwardchangecriterion %}
{% csrf_token %}
{% endif %}
{% else %}

{% trans "No criteria yet." %}

{% endif %} {% if perms.forward_netbox.add_forwardchangecriterion %} {% trans "Add criterion" %} {% endif %}
{% trans "Evidence" %}

{% trans "This is what the verdict is computed from, and the only thing it is computed from." %}

{% if criterion_outcomes %} {% for outcome in criterion_outcomes %} {% endfor %}
{% trans "Criterion" %} {% trans "Before" %} {% trans "After" %} {% trans "Result" %}
{{ outcome.criterion.name }} {% if not outcome.blocking %} {% trans "advisory" %} {% endif %} {% checkmark outcome.before_passed %} {% checkmark outcome.after_passed %} {% if outcome.flip %} {% if outcome.blocks %} {% trans "regression" %} {% else %} {{ outcome.flip }} {% endif %} {% else %} {# Never rendered as a pass. A criterion nobody #} {# evaluated is not a criterion that passed. #} {% trans "not measured" %} {% endif %}
{% else %}

{% trans "No criteria have been evaluated. A change with nothing asserted cannot be verified, only asserted to have happened." %}

{% endif %}
{% trans "Validation" %}

{% trans "Rules from the validation policies that apply to this change, run in its workspace. A run counts only against the snapshots the change is pinned to now." %}

{% if validation_gate %}
{% for blocker in validation_gate %}
{{ blocker }}
{% endfor %}
{% endif %} {% if validation_runs %} {% for run in validation_runs %} {% endfor %}
#{{ run.pk }} {% badge run.get_status_display bg_color=run.get_status_color %} {% if run.score is None %}{% trans "no score" %}{% else %}{{ run.score }}{% endif %} {{ run.created|date:"Y-m-d H:i" }}
{% else %}

{% trans "No validation runs yet." %}

{% endif %} {% if can_validate %}
{% csrf_token %}
{% endif %}
{% trans "What Forward allows here" %}

{% trans "Found by asking Forward with read-only calls, never from a licence table." %}

{% if capabilities %} {% for label, text, available in capabilities %} {% endfor %}
{{ label }} {{ text }}
{% endif %}
{% csrf_token %}
{{ predict_panel.heading }}
{# Advisory, and separated from Evidence above on purpose. An #} {# advisory panel that looks like evidence is how a prediction #} {# quietly becomes a decision. #}

{% trans "Advisory only. The verdict does not depend on this." %}

{{ predict_panel.message }}

{% if predict_panel.stale %}

{% trans "The proposal changed after this prediction, so it describes an earlier proposal." %}

{% endif %} {% if predict_snapshot_id %}

{% trans "Predicted snapshot" %}: {{ predict_snapshot_id }}

{% endif %} {% if predict_panel.scope %}

{{ predict_panel.scope }}

{% endif %} {% if perms.forward_netbox.change_forwardchange %} {% if predict_pending %}
{% csrf_token %}
{% elif predict_offered and can_predict %} {% trans "Ask Forward to predict" %} {% elif not predict_offered %}

{% trans "Predict is off for this source. Turn on Ask Forward to predict change-control changes on the source to offer it." %}

{% endif %} {% endif %}
{% if proposal_rows and perms.forward_netbox.change_forwardchange %}
{% trans "Proposal" %}

{% trans "CLI commands to try on each device. They are kept in NetBox for this change only and are not written to the change log, the journal or the support bundle." %}

{% if proposal_editable %}
{% csrf_token %} {% for name, commands in proposal_rows %}
{% endfor %}
{% else %}

{% trans "The proposal can no longer be edited." %}

{% endif %}
{% endif %}
{% trans "Scope" %}
{% if scoped_devices %}
    {% for scoped in scoped_devices %}
  • {{ scoped.device|linkify }} {% if scoped.forward_device_key %} {{ scoped.forward_device_key }} {% else %} {% trans "no Forward identity" %} {% endif %} {% if perms.forward_netbox.change_forwardchange and not object.workspace_network_id %}
    {% csrf_token %}
    {% endif %}
  • {% endfor %}
{% else %}

{% trans "No devices in scope." %}

{% endif %} {% if perms.forward_netbox.change_forwardchange and not object.workspace_network_id %}
{% csrf_token %}
{% if scoped_devices %}
{% csrf_token %}
{% endif %} {% endif %}
{% trans "Forward workspace and verification" %}
{# The workspace holds only this change's devices; queries run on it, at #} {# the baseline it was forked with and a collection made after the apply. #}
{% trans "Workspace" %} {% if object.workspace_network_id %} {{ object.workspace_network_id }} ({{ object.workspace_origin }}) {% else %} {% trans "none yet" %} {% endif %}
{% trans "Baseline snapshot" %} {{ object.before_snapshot_id|placeholder }}
{% trans "After snapshot" %} {{ object.after_snapshot_id|placeholder }}
{% if perms.forward_netbox.change_forwardchange %} {% if not object.workspace_network_id %}
{% csrf_token %}
{% csrf_token %}
{% else %}
{% csrf_token %}
{% if object.state == 'applied' or object.state == 'collected' %} {% trans "Collect a snapshot..." %} {% endif %}
{% csrf_token %}
{% csrf_token %}
{% endif %} {% endif %}
{% trans "Reviews" %}
{# Two gates. The pre gate approves the PLAN; the post gate approves #} {# the CLOSURE, which is where the branch actually merges. A PROCEED #} {# verdict opens that gate rather than walking through it. #} {% if reviews %} {% for review in reviews %} {% endfor %}
{{ review.reviewer|placeholder }} {{ review.phase }} {% if review.approved %} {% trans "approved" %} {% else %} {% trans "rejected" %} {% endif %} {{ review.comment }}
{% else %}

{% trans "No reviews yet." %}

{% endif %} {% if perms.forward_netbox.approve_forwardchange %}
{% csrf_token %}
{% endif %}
{% trans "Comments" %}
{% for comment in comments %}
{{ comment.author|placeholder }} ยท {{ comment.created|date:"Y-m-d H:i" }} {% if comment.resolved %} {% trans "resolved" %} {% endif %}
{{ comment.body|linebreaksbr }}
{% for reply in comment.replies.all %}
{{ reply.author|placeholder }}: {{ reply.body|linebreaksbr }}
{% csrf_token %} {% if request.user.is_authenticated and reply.author_id == request.user.pk %} {% endif %}
{% endfor %} {% if can_reply %}
{% csrf_token %}
{% endif %}
{% if can_resolve %}
{% csrf_token %}
{% endif %} {% if request.user.is_authenticated and comment.author_id == request.user.pk and not comment.replies.all %}
{% csrf_token %}
{% endif %}
{% empty %}

{% trans "No comments yet." %}

{% endfor %} {% if can_comment %}
{% csrf_token %}
{% endif %}
{% trans "Next steps" %}
{% if transitions %} {% for transition in transitions %} {% endfor %}
{{ transition.target }} {% if transition.allowed %} {% trans "ready" %} {% else %}
    {% for blocker in transition.blockers %}
  • {{ blocker }}
  • {% endfor %}
{% endif %}
{% else %}

{% trans "This change is finished; there is nowhere further to go." %}

{% endif %}
{% endblock content %}