Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions .github/workflows/parity-governance.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# These are the scanner's own unit and repository-acceptance tests.
# These are the scanner's own unit and repository-acceptance tests, plus the
# explicitly named legacy R-R preservation contract. Keep that companion test
# exact rather than widening back to every Tools/tests file.
#
# On a PULL REQUEST, run the multi-minute suite only when its implementation,
# authority, tests or workflow change; ordinary product-source changes belong to
# the later ledger/ratchet gate, not to repeated self-testing of the scanner.
# authority, named protection tests or workflow change; ordinary product-source
# changes belong to the later ledger/ratchet gate, not to repeated self-testing
# of the scanner.
#
# It also runs DAILY on main. One of these tests is not about the scanner at
# all: the repository acceptance asserts that the checked-in authority still
Expand All @@ -29,15 +32,17 @@ on:
- 'Tools/issue_ref.py'
- 'Tools/parity_*.py'
- 'Tools/parity_*.json'
- 'Tools/tests/**'
- 'Tools/tests/test_parity_*.py'
- 'Tools/tests/test_rr_legacy_preservation_contract.py'
- '.github/workflows/parity-governance.yml'
push:
branches: [main]
paths:
- 'Tools/issue_ref.py'
- 'Tools/parity_*.py'
- 'Tools/parity_*.json'
- 'Tools/tests/**'
- 'Tools/tests/test_parity_*.py'
- 'Tools/tests/test_rr_legacy_preservation_contract.py'
- '.github/workflows/parity-governance.yml'
# Daily, so authority staleness is found on a schedule instead of by whoever
# next edits Tools/. See the header note.
Expand All @@ -64,11 +69,15 @@ jobs:
- name: Run parity-governance tests
run: |
set -o pipefail
python3 -m unittest discover -s tests -p "test_*.py" -v 2>&1 | tee "$RUNNER_TEMP/out.txt"
python3 -m unittest -v \
tests.test_parity_ledger \
tests.test_parity_governance_acceptance \
tests.test_rr_legacy_preservation_contract \
2>&1 | tee "$RUNNER_TEMP/out.txt"
ran=$(grep -oE '^Ran [0-9]+ test' "$RUNNER_TEMP/out.txt" | grep -oE '[0-9]+')
echo "collected ${ran:-0} tests"
if [ "${ran:-0}" -lt 103 ]; then
echo "::error::expected at least 103 parity-governance tests, collected ${ran:-0} — discovery is broken, not the suite"
if [ "${ran:-0}" -lt 113 ]; then
echo "::error::expected at least 113 parity-governance tests, collected ${ran:-0} — discovery is broken, not the suite"
exit 1
fi
working-directory: Tools
14 changes: 14 additions & 0 deletions Tools/PARITY_GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ edits `parity_dispositions.json`.
Deleting a paired duplicate is fail-closed derived-snapshot drift that requires
this guarded refresh flow by design.

If the scheduled `main` run finds that the exact base already contains stale
metadata, use the explicit repair variant:

```sh
python3 Tools/parity_ledger.py --refresh-derived --repair-stale-base --base origin/main
```

This is not a force switch and does not create a disposition. The ratchet
independently scans the exact base and the current tree, then permits the repair
only when semantic authority, finding identities, counters, and typed
dispositions are unchanged between them and both regenerated snapshots exactly
match that shared state. Any source debt introduced on the repair branch still
fails closed, as does a hand-edited or partial snapshot.

For a new one-sided declaration, choose explicitly:

The `add-unpaired-function` diagnostic defines "new" by absence of the
Expand Down
10 changes: 10 additions & 0 deletions Tools/parity_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2620,6 +2620,11 @@ def main(argv: list[str] | None = None) -> int:
parser.add_argument("--bootstrap-map", action="store_true", help="write a fresh inventory map before scanning")
parser.add_argument("--write-baseline", action="store_true", help="rewrite the baseline with current findings")
parser.add_argument("--refresh-derived", action="store_true", help="refresh existing derived snapshots only if the ratchet accepts the result")
parser.add_argument(
"--repair-stale-base",
action="store_true",
help="with --refresh-derived, repair metadata drift already present in the exact base",
)
parser.add_argument("--base", default="origin/main", help="exact git ref used to prove debt reductions")
args = parser.parse_args(argv)

Expand All @@ -2629,6 +2634,9 @@ def main(argv: list[str] | None = None) -> int:
if args.bootstrap_map != args.write_baseline:
print("FAIL --bootstrap-map and --write-baseline must be used together")
return 2
if args.repair_stale_base and not args.refresh_derived:
print("FAIL --repair-stale-base requires --refresh-derived")
return 2
if args.refresh_derived:
if args.bootstrap_map or args.write_baseline or args.no_baseline:
print("FAIL --refresh-derived cannot be combined with bootstrap, baseline, or display modes")
Expand All @@ -2654,6 +2662,8 @@ def main(argv: list[str] | None = None) -> int:
sys.executable, str(Path(__file__).with_name("parity_ratchet.py")),
"--root", str(root), "--base", args.base, "--offline",
]
if args.repair_stale_base:
command.append("--repair-stale-base")
completed = subprocess.run(command, cwd=root, text=True, capture_output=True)
if completed.returncode:
print("FAIL derived refresh rejected; snapshots restored")
Expand Down
2 changes: 1 addition & 1 deletion Tools/parity_ledger_baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
{"rule": "test-only-callsite", "scope": "Packages/StrandAnalytics", "reason": "Exact declaration is reached only from tests in the conservative lexical call graph; dynamic, callback and external entry points are intentionally not inferred.", "provenance": "Exact identities emitted by parity_ledger rule test-only-callsite for current upstream scope Packages/StrandAnalytics; no wildcard matching.", "count": 44, "identities_sha256": "fff777acbc66e7dad92bd0a6c8b7644e5c7ab6ddc0a7106dfecc49319da6127b"},
{"rule": "test-only-callsite", "scope": "Packages/StrandImport", "reason": "Exact declaration is reached only from tests in the conservative lexical call graph; dynamic, callback and external entry points are intentionally not inferred.", "provenance": "Exact identities emitted by parity_ledger rule test-only-callsite for current upstream scope Packages/StrandImport; no wildcard matching.", "count": 13, "identities_sha256": "2ee99bb3c9fe8ab244063600cd863f41344b26780bcb73b814c13cd26aea9413"},
{"rule": "test-only-callsite", "scope": "Packages/WhoopProtocol", "reason": "Exact declaration is reached only from tests in the conservative lexical call graph; dynamic, callback and external entry points are intentionally not inferred.", "provenance": "Exact identities emitted by parity_ledger rule test-only-callsite for current upstream scope Packages/WhoopProtocol; no wildcard matching.", "count": 30, "identities_sha256": "a9dc115534e8710217db9cd39bcceaa1419741737bfd3b733007dc0215cd5af2"},
{"rule": "test-only-callsite", "scope": "Packages/WhoopStore", "reason": "Exact declaration is reached only from tests in the conservative lexical call graph; dynamic, callback and external entry points are intentionally not inferred.", "provenance": "Exact identities emitted by parity_ledger rule test-only-callsite for current upstream scope Packages/WhoopStore; no wildcard matching.", "count": 37, "identities_sha256": "2a62c1ece1c541b662dd407f0668d9a02782bb4d21f9bb181518272ff12f0e40"},
{"rule": "test-only-callsite", "scope": "Packages/WhoopStore", "reason": "Exact declaration is reached only from tests in the conservative lexical call graph; dynamic, callback and external entry points are intentionally not inferred.", "provenance": "Exact identities emitted by parity_ledger rule test-only-callsite for current upstream scope Packages/WhoopStore; no wildcard matching.", "count": 51, "identities_sha256": "82cb0bf1f7db514ca69dc7a1394b0de01d7ae4d7d1f948754da52c5b29e01277"},
{"rule": "test-only-callsite", "scope": "android/analytics", "reason": "Exact declaration is reached only from tests in the conservative lexical call graph; dynamic, callback and external entry points are intentionally not inferred.", "provenance": "Exact identities emitted by parity_ledger rule test-only-callsite for current upstream scope android/analytics; no wildcard matching.", "count": 66, "identities_sha256": "edae212c4a4da22b0f11432e83d6a9b8abc21d0649d16026b439686681add718"},
{"rule": "test-only-callsite", "scope": "android/data", "reason": "Exact declaration is reached only from tests in the conservative lexical call graph; dynamic, callback and external entry points are intentionally not inferred.", "provenance": "Exact identities emitted by parity_ledger rule test-only-callsite for current upstream scope android/data; no wildcard matching.", "count": 14, "identities_sha256": "9bab871f5ddbc1d2634805bf525888125c3880d160ab09b3ee7f3d7af6c487cd"},
{"rule": "test-only-callsite", "scope": "android/ingest", "reason": "Exact declaration is reached only from tests in the conservative lexical call graph; dynamic, callback and external entry points are intentionally not inferred.", "provenance": "Exact identities emitted by parity_ledger rule test-only-callsite for current upstream scope android/ingest; no wildcard matching.", "count": 12, "identities_sha256": "d4f45d66087768eab133b918af6658b06ec1d91305e38fb045f6ae1629c94678"},
Expand Down
47 changes: 43 additions & 4 deletions Tools/parity_ratchet.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ def compare_metadata(
base: str,
*,
offline: bool,
repair_stale_base: bool = False,
warnings: list[str] | None = None,
) -> list[str]:
"""Compare current governance metadata with the exact requested base."""
Expand Down Expand Up @@ -440,12 +441,39 @@ def compare_metadata(
with _base_tree(root, base) as base_root:
base_sets = parity_ledger.semantic_authority(base_root)
base_manifest = parity_ledger.authority_manifest(base_sets)
if old_map["authority"] != base_manifest:
base_scan_map = parity_ledger.build_compact_twin_map(base_root)
base_scan_map["exemptions"] = old_registry["dispositions"]
base_scan = parity_ledger.scan(base_root, base_scan_map)
base_authority_is_stale = old_map["authority"] != base_manifest
if base_authority_is_stale:
repair_mismatches: list[str] = []
if current_sets != base_sets:
repair_mismatches.append("semantic authority differs from the exact base")
if ({item.identity for item in current_scan.findings}
!= {item.identity for item in base_scan.findings}):
repair_mismatches.append("finding identities differ from the exact base")
if current_scan.counters != base_scan.counters:
repair_mismatches.append("counters differ from the exact base")
if current_registry != old_registry:
repair_mismatches.append("typed dispositions differ from the exact base")
if current_map["authority"] != current_manifest:
repair_mismatches.append("current authority is not exactly derived")
if current_baseline != parity_ledger.build_compact_baseline(current_scan):
repair_mismatches.append("current baseline is not exactly derived")
if not repair_stale_base:
errors.append(
f"{TWIN_MAP_PATH}: base authority cannot be reproduced with the current derivation; migration required"
)
base_scan_map = parity_ledger.build_compact_twin_map(base_root)
base_scan = parity_ledger.scan(base_root, base_scan_map)
elif repair_mismatches:
errors.append(
f"{TWIN_MAP_PATH}: stale-base repair rejected because "
+ "; ".join(repair_mismatches)
)
else:
warnings.append(
f"{TWIN_MAP_PATH}: repaired stale metadata already present in the exact base; "
"no current-tree governance delta accepted"
)
base_findings = {item.identity for item in base_scan.findings}
current_findings = {item.identity for item in current_scan.findings}
required = _required_v3_exemptions(
Expand Down Expand Up @@ -616,13 +644,24 @@ def main(argv: list[str] | None = None) -> int:
parser.add_argument("--root", type=Path, default=ROOT)
parser.add_argument("--base", help="base ref; defaults durably to origin/main")
parser.add_argument("--offline", action="store_true", help="skip GitHub issue existence checks")
parser.add_argument(
"--repair-stale-base",
action="store_true",
help="adopt exactly derived metadata only when governed state is unchanged from an already-stale base",
)
args = parser.parse_args(argv)
root = args.root.resolve()
try:
base = resolve_base(root, args.base)
warnings: list[str] = []
errors = repository_consistency_errors(root, warnings=warnings)
errors.extend(compare_metadata(root, base, offline=args.offline, warnings=warnings))
errors.extend(compare_metadata(
root,
base,
offline=args.offline,
repair_stale_base=args.repair_stale_base,
warnings=warnings,
))
for warning in warnings:
print(f"WARNING: {warning}", file=sys.stderr)
return _print_errors(errors)
Expand Down
18 changes: 9 additions & 9 deletions Tools/parity_twin_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@
]
},
"authority": {
"files": {"count": 487, "sha256": "a90bd1677a35dae57901a6e193e4ec9b5648a004d6bcce796cb56dccc2648e66"},
"functions": {"count": 4315, "sha256": "d2bf60097e1a84455b3ab07fcc707973829d3be465adf26686188f8e464ab2cf"},
"properties": {"count": 448, "sha256": "e90524140aa597cab434483743950f0b78110140d1e2b1dd9ed7d72e8d25fddf"},
"constants": {"count": 1890, "sha256": "d918e6c0739ae1d2df2f74bfdbd36f6e7f26c434ea4dc09a1afe4eb75c8f8d11"},
"files": {"count": 490, "sha256": "554e510e220e3bf87187abc422178216f31c855311cb093434bda155e9283f96"},
"functions": {"count": 4353, "sha256": "dc31ce701cf0ee2ec1992a21da290712ee63cfa0268078051fd71878dd2671bf"},
"properties": {"count": 451, "sha256": "cb176d7203c8c42522f55d0db9acdcde97ed7cad5e7225cc6a41b18206495c8d"},
"constants": {"count": 1893, "sha256": "9687e3e36f209bf54e0f78e8e0d0cbf4e21d148c47ed1e4cac3b0645c60b53ff"},
"file_pairs": {"count": 61, "sha256": "ed77a9b1fadece7a30b27b37cb1826a2e95ac1b5e81419fa5364ae6dffc22bf3"},
"function_pairs": {"count": 152, "sha256": "a8723d1c2bd163b6c128350fb8c169a7325b3a5e3dff877362f8a2094d70f3f4"},
"function_pairs": {"count": 153, "sha256": "a4ed8f320c9da4e8838b605095f5f82d58abd98fae3ece0e30ce4db4898d5dc6"},
"property_pairs": {"count": 147, "sha256": "781073f4b7af31db4400da247eb16910c2c8adbbb4ab5f9169b852566ed3dbc8"},
"constant_pairs": {"count": 665, "sha256": "080c97b062e80cb7d1f526bfb50b367cbbf6b0cadf43e2c5ec3f2ad046fe09cb"},
"unpaired_files": {"count": 383, "sha256": "af7ea6779645ef49bedf7c8db1b67b95e493daa96215fd0bcfb86c7db78618f4"},
"unpaired_functions": {"count": 4017, "sha256": "f27d4ce4d5033c98ee900c0eb60399507354320b0edd2a35fdfba96f458db5f4"},
"unpaired_properties": {"count": 154, "sha256": "e38ff018589491eedd40569390492f97f5d9dd2a12acd8a2880a34d1d58c73e8"},
"unpaired_constants": {"count": 560, "sha256": "7edadfead59eb823725ed319636e573e73f623e4e4ce39ac85144d2b3531f787"}
"unpaired_files": {"count": 386, "sha256": "a2edbca2181629394366a14ad4562bbaa82b2b9db0d729e43f3a8f1836f255cd"},
"unpaired_functions": {"count": 4053, "sha256": "ee636450c185d53eb6e6754d41890a07f8d24412c07edeec859492f9cafe4c39"},
"unpaired_properties": {"count": 157, "sha256": "bd4cab9d2ebec6da6ed6ff95599b0ab8cd3b4ca7a0c2c3e82493280940d8fda9"},
"unpaired_constants": {"count": 563, "sha256": "0ada9824528f78eba95c65c56ce1b744d9d5bb876ebb2ca75f6d0b02874e89e8"}
}
}
Loading
Loading