Spaces:
Sleeping
Sleeping
| """The interrogation firewall — the single source of truth for forbidden tokens. | |
| SIP *measures*; it never *judges*. A SIP evidence bundle must therefore never | |
| carry a verdict: no pass/fail, accepted, validated, credible, score, or any | |
| other field that encodes a credibility decision. The credibility decision | |
| belongs to the practitioner and the COU acceptance criteria, never to the | |
| measurement instrument (``SIP_Evidence_Contract_Spec_v0_1.md`` §8). | |
| This module is the ONE place the forbidden vocabulary is defined. Every other | |
| enforcement point imports ``FORBIDDEN_TOKENS`` from here so the schema, the | |
| ``interrogate`` command output check, the CI ``firewall_guard.py`` script, and | |
| the firewall tests can never drift from one another: | |
| - ``specs/sip_evidence_bundle_schema.json`` rejects any bundle whose property | |
| names intersect this list (and rejects unknown properties outright via | |
| ``additionalProperties: false``); a lockstep test asserts the schema's | |
| root denylist equals this list. | |
| - ``uofa interrogate`` asserts none of these tokens name a field in its output. | |
| - ``dev/tools/scripts/firewall_guard.py`` scans the emitted surface for them. | |
| - ``tests/interrogate/`` parametrizes over this list. | |
| See ``AGENTS.md`` §12 (the interrogation firewall). | |
| Matching is on **exact property names**, not substrings: ``decision`` is | |
| forbidden as a bundle field, but ``parentModelSnapshot.parentDecision`` — the | |
| recorded decision of the *parent* COU, carried as inherited provenance per | |
| SIP §5.6 — is legitimate. Substring matching would false-positive on it. | |
| """ | |
| from __future__ import annotations | |
| # Verdict / decision / rating vocabulary that must never name a field in a SIP | |
| # evidence bundle. Exact, lowercase-or-camel as authored; see module docstring | |
| # for why matching is exact-property-name, not substring. | |
| FORBIDDEN_TOKENS: tuple[str, ...] = ( | |
| "verdict", | |
| "passFail", | |
| "pass_fail", | |
| "accepted", | |
| "rejected", | |
| "validated", | |
| "credible", | |
| "credibilityIndex", | |
| "decision", | |
| "outcome", | |
| "certify", | |
| "certified", | |
| "score", | |
| "rating", | |
| "approved", | |
| ) | |
| def forbidden_property_names_constraint() -> dict: | |
| """Return the JSON-Schema fragment that bans forbidden property names. | |
| The schema embeds this fragment (``propertyNames``) at the root and on | |
| every freeform object. A test asserts the on-disk schema's root denylist | |
| equals ``list(FORBIDDEN_TOKENS)`` so the two cannot drift. | |
| """ | |
| return {"not": {"enum": list(FORBIDDEN_TOKENS)}} | |
| # The regions where decision/action content is legitimate (Addendum A4/A5, | |
| # generalized for the pack-shaped legs): top-level blocks each governed by their | |
| # OWN signature scope, not the denylist. ``engineerDecision`` is the original (a | |
| # signed human decision); ``guardrailAction`` is the guardrail leg's output (§6); | |
| # downstream verified-outcome labels can add more. Each is excluded from the | |
| # measurement signature and exempt from the measurement-region denylist — | |
| # additive content in its own scope, never mixed into the measurement region. | |
| DECISION_BLOCK_KEY = "engineerDecision" | |
| GUARDRAIL_BLOCK_KEY = "guardrailAction" | |
| ACTION_REGION_KEYS: tuple[str, ...] = (DECISION_BLOCK_KEY, GUARDRAIL_BLOCK_KEY) | |
| def find_forbidden_property_names(obj, _path: str = "$"): | |
| """Yield ``(json_path, token)`` for every forbidden property name in ``obj``. | |
| Walks dicts/lists recursively, matching property *names* exactly (so | |
| ``parentDecision`` is never flagged for containing ``decision``). Used by | |
| the command-level firewall check and the CI guard to inspect an emitted | |
| bundle structurally rather than by substring grep. | |
| """ | |
| forbidden = set(FORBIDDEN_TOKENS) | |
| if isinstance(obj, dict): | |
| for key, value in obj.items(): | |
| if key in forbidden: | |
| yield (f"{_path}.{key}", key) | |
| yield from find_forbidden_property_names(value, f"{_path}.{key}") | |
| elif isinstance(obj, list): | |
| for index, item in enumerate(obj): | |
| yield from find_forbidden_property_names(item, f"{_path}[{index}]") | |
| def find_forbidden_in_measurement_region(bundle): | |
| """Forbidden property names anywhere EXCEPT the top-level action-region blocks. | |
| The signature-scoped firewall (Addendum A5, superseding the flat denylist): | |
| decision/action content is valid only inside a verifying-signed action-region | |
| block (``ACTION_REGION_KEYS`` — ``engineerDecision``, ``guardrailAction``, …) | |
| and is forbidden everywhere else — the "measurement region". This walker | |
| enforces the "everywhere else" half. Only the *top-level* action-region blocks | |
| are exempt; such a block smuggled deeper (e.g. inside ``measurements``) is | |
| still fully scanned, so it cannot be used to bypass the firewall. | |
| """ | |
| if not isinstance(bundle, dict): | |
| yield from find_forbidden_property_names(bundle) | |
| return | |
| forbidden = set(FORBIDDEN_TOKENS) | |
| exempt = set(ACTION_REGION_KEYS) | |
| for key, value in bundle.items(): | |
| if key in exempt: | |
| continue | |
| if key in forbidden: | |
| yield (f"$.{key}", key) | |
| yield from find_forbidden_property_names(value, f"$.{key}") | |