Skip to content

fix: bind schema commits to pristine gate receipts - #3815

Merged
Sinity merged 8 commits into
masterfrom
feature/feat/schema-inference-receipt-handoff
Aug 6, 2026
Merged

fix: bind schema commits to pristine gate receipts#3815
Sinity merged 8 commits into
masterfrom
feature/feat/schema-inference-receipt-handoff

Conversation

@Sinity

@Sinity Sinity commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

Schema commits now require a recomputable hard-gate receipt bound to the configured ArchiveLocation and live archive evidence. Campaign manifests revalidate the live registry and classifier, generate a real SyntheticCorpus witness, and admit only executable selections.

Problem

A receipt could retain PASS after hard-gate query or blob evidence was mutated and its top-level digest recomputed. Commit targeting inferred the archive from db_path.parent, which is wrong for configured and pointer-based archive layouts. Persisted campaign manifests could outlive registry package or classifier changes, and an all-unsupported campaign could have no executable selection.

Solution

  • Added a canonical hard_gate_evidence_digest over the exact source-query and full BlobStore verification evidence. Receipt validation recomputes schema identity, source gates, corpus fidelity, ground-truth reconciliation, blob verification, and active ArchiveLocation paths before accepting a commit.
  • Added archive_root to SchemaCommitRequest. Existing handoffs are checked against the accepted gate before generation, and stale gate or handoff combinations fail before package writes.
  • Revalidated persisted package/version/element hashes, coverage decisions, classifier output, unsupported decisions, and generator schemas against the live registry.
  • Constructed a real SyntheticCorpus witness for each executable manifest entry and rejected campaigns with no executable synthetic corpus selection.
  • Added mutation-sensitive real-route tests for forged query evidence with a recomputed digest, blob evidence, archive identity, package and schema content, classifier keys, unsupported decisions, dry-run handoff preservation, and all-unsupported campaigns.

Verification

  • direnv exec . devtools test tests/unit/schemas/test_operator_commit.py tests/unit/schemas/test_inferred_corpus_manifest.py tests/unit/maintenance/test_schema_inference_gate.py tests/unit/devtools/test_schema_commit_command.py tests/property/test_inferred_corpus_loop.py
  • Result: 85 passed.
  • direnv exec . devtools verify --quick
  • Result: Ruff, strict mypy, generated surfaces, layering, schema policy, and promotion audit all passed.
  • Commit: 0e38656f1, pushed to the existing feature branch.

Residual production prerequisite

A real schema commit still requires an operator-generated fresh PASS receipt from devtools verify schema-inference-gate for the intended configured archive and its external ground-truth roots. Tests use only temporary seeded archives and do not access production.

Summary by CodeRabbit

  • New Features

    • Schema commits now require a valid schema-inference gate receipt.
    • Successful commits generate verifiable handoff receipts with coverage and integrity details.
    • Dry runs preview generated handoffs without changing live output.
    • Campaign validation checks schema support, package integrity, provider coverage, and receipt consistency.
    • Unsupported schema constructs are identified consistently during validation.
  • Bug Fixes

    • Invalid, expired, mismatched, or tampered receipts are rejected with clearer CLI errors.
    • Commit results and JSON output now include handoff receipt details when available.

Problem
Schema commits accepted no durable handoff from the pristine schema-inference gate, so inferred-corpus campaign loading could proceed from catalog-only data without binding package contents or explicit unsupported decisions.

What changed
Add an immutable content-addressed handoff receipt for the accepted gate digest, origin/provider coverage, persisted package/version/element hashes, and unsupported or nonrepresentable elements. Require the receipt at schema commit time, aggregate it beside the registered packages, expose it through the operator result and CLI, and validate it against the real registry when campaign manifests are compiled or loaded. Ref polylogue-tnqqt and polylogue-r9xsj.

Compatibility/migration
The schema commit CLI now requires --schema-inference-gate-receipt. Existing catalog-only manifest reads remain available outside campaign mode. No reindex/rebuild modules or wire formats changed.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The schema commit flow now requires an accepted gate receipt, creates a content-addressed inference handoff, persists it, and validates it during campaign manifest compilation. Dry runs return staged handoffs without modifying the real output directory.

Changes

Schema inference receipt workflow

Layer / File(s) Summary
Receipt contract and persistence
polylogue/schemas/operator/receipt.py
Adds immutable receipt records, canonical digests, package hashing, coverage decisions, representability checks, registry construction, and atomic JSON persistence.
Authoritative gate validation
polylogue/maintenance/schema_inference_gate.py, tests/unit/maintenance/test_schema_inference_gate.py
Adds receipt freshness, archive identity, evidence digest, nonce, and PASS-receipt validation.
Commit gate and handoff
polylogue/schemas/operator/models.py, polylogue/schemas/operator/commit.py, devtools/schema_commit.py, tests/unit/schemas/test_operator_commit.py, tests/unit/devtools/test_schema_commit_command.py
Requires and validates gate receipts. Builds and merges handoffs, supports dry-run staging, exposes receipt data in results, and updates CLI forwarding and output.
Campaign manifest verification
polylogue/schemas/synthetic/classification.py, tests/infra/inferred_corpus.py, tests/unit/schemas/test_inferred_corpus_manifest.py
Adds canonical construct classification and campaign-mode validation for package hashes, coverage, executable witnesses, and unsupported-entry decisions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant SchemaCommitCLI
  participant commit_provider_schema
  participant SchemaInferenceReceipt
  participant InferredCorpusManifest
  Operator->>SchemaCommitCLI: provide accepted gate receipt path
  SchemaCommitCLI->>commit_provider_schema: submit SchemaCommitRequest
  commit_provider_schema->>SchemaInferenceReceipt: build and persist handoff
  SchemaInferenceReceipt-->>commit_provider_schema: return digest and path
  commit_provider_schema-->>SchemaCommitCLI: return SchemaCommitResult
  InferredCorpusManifest->>SchemaInferenceReceipt: load persisted handoff
  InferredCorpusManifest-->>Operator: accept or reject campaign manifest
Loading

Possibly related PRs

  • Sinity/polylogue#3538: Directly modifies the schema commit CLI, models, commit workflow, and related tests.
  • Sinity/polylogue#3797: Directly modifies inferred-corpus manifest compilation, validation, and handoff logic.
  • Sinity/polylogue#3830: Directly modifies schema-inference gate receipt validation and the schema-commit CLI.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: binding schema commits to validated gate receipts.
Description check ✅ Passed The description covers the main template sections and verification, but it omits an explicit Unreleased changelog entry for user-visible CLI changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/feat/schema-inference-receipt-handoff

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
tests/unit/schemas/test_operator_commit.py (1)

261-269: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the dry run leaves the persisted handoff untouched.

_commit_into calls write_schema_inference_receipt unconditionally. The dry run is safe only because commit_provider_schema redirects output_dir to a temporary staging root. This test proves that invariant for catalog.json and the element schema, but not for schema-inference-handoff.json.

Add the same before/after byte comparison for the handoff file. It pins the invariant that the dry-run staging redirect exists to guarantee.

💚 Proposed added assertion
         catalog_before_bytes = (output_dir / _PROVIDER / "catalog.json").read_bytes()
+        handoff_before_bytes = (output_dir / SCHEMA_INFERENCE_HANDOFF_FILENAME).read_bytes()
         # The real committed directory was never touched.
         assert (output_dir / _PROVIDER / "catalog.json").read_bytes() == catalog_before_bytes
+        assert (output_dir / SCHEMA_INFERENCE_HANDOFF_FILENAME).read_bytes() == handoff_before_bytes
         on_disk = _read_element_schema(output_dir, "v1")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/schemas/test_operator_commit.py` around lines 261 - 269, Extend
the dry-run assertion block in commit_provider_schema to also verify
schema-inference-handoff.json is unchanged on disk, using the same before/after
byte comparison pattern already used for catalog.json. Keep the focus on the
existing commit_result, _request(..., dry_run=True), and _read_element_schema
checks, and add the handoff-file comparison at the same level so the test
confirms the staging redirect preserves the persisted handoff.
devtools/schema_commit.py (1)

90-101: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle the new ValueError from gate receipt validation.

_accepted_gate_receipt_digest raises ValueError when the receipt is unreadable, uses the wrong schema, or has a non-PASS verdict. commit_provider_schema on line 90 is not inside a try block. Line 80 wraps only build_schema_privacy_config.

The most likely new failure path therefore prints a Python traceback and exits with the interpreter's code, instead of the schema-commit: <message> line and exit 1 that this command uses everywhere else. With --json, the traceback also replaces the JSON error envelope, so machine consumers get no parseable output.

🛡️ Proposed fix
-    result = commit_provider_schema(
-        SchemaCommitRequest(
-            provider=str(args.provider),
-            output_dir=output_dir,
-            db_path=get_config().db_path,
-            max_samples=args.max_samples,
-            privacy_config=privacy_config,
-            full_corpus=bool(args.full_corpus),
-            dry_run=bool(args.dry_run),
-            schema_inference_gate_receipt_path=args.schema_inference_gate_receipt,
-        )
-    )
+    try:
+        result = commit_provider_schema(
+            SchemaCommitRequest(
+                provider=str(args.provider),
+                output_dir=output_dir,
+                db_path=get_config().db_path,
+                max_samples=args.max_samples,
+                privacy_config=privacy_config,
+                full_corpus=bool(args.full_corpus),
+                dry_run=bool(args.dry_run),
+                schema_inference_gate_receipt_path=args.schema_inference_gate_receipt,
+            )
+        )
+    except ValueError as exc:
+        if args.json:
+            print(json.dumps({"provider": str(args.provider), "success": False, "error": str(exc)}, sort_keys=True))
+        else:
+            print(f"schema-commit: {exc}", file=sys.stderr)
+        return 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@devtools/schema_commit.py` around lines 90 - 101, Wrap the
commit_provider_schema call in the command’s existing error-handling flow so
ValueError from _accepted_gate_receipt_digest is caught and rendered through the
standard schema-commit: <message> output with exit code 1. Preserve the --json
error envelope behavior, and leave the separate build_schema_privacy_config
handling unchanged.
tests/unit/devtools/test_schema_commit_command.py (1)

106-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the new handoff output.

Every SchemaCommitResult stub in this file omits handoff, so it defaults to None. The new print block at devtools/schema_commit.py lines 117-120 therefore never runs in any test. The handoff_digest and handoff_path lines are operator-visible output with no coverage.

The JSON test is the natural place to pin this, because to_dict now emits handoff and handoff_path. Populate handoff in this stub and assert both keys.

💚 Proposed added coverage
+_HANDOFF = SchemaInferenceReceipt(
+    gate_receipt_digest="a" * 64,
+    coverage_decisions=(
+        SchemaInferenceCoverageDecision(
+            origin="chatgpt", provider="chatgpt", decision="committed", reason="hashes recorded"
+        ),
+    ),
+    packages=(),
+)
         lambda request: SchemaCommitResult(
             provider=request.provider,
             generation=GenerationResult(provider=request.provider, schema={"type": "object"}, sample_count=42),
             versions=(
                 SchemaVersionCommitReport(
                     version="v2", status="changed", sample_count=42, added_paths=("session_document.new",)
                 ),
             ),
             dry_run=False,
+            handoff=_HANDOFF,
+            handoff_path=tmp_path / "handoff.json",
         ),
     )
     assert payload["versions"][0]["added_paths"] == ["session_document.new"]
+    assert payload["handoff"]["gate_receipt_digest"] == "a" * 64
+    assert payload["handoff_path"] == str(tmp_path / "handoff.json")

Add a matching non-JSON test that asserts handoff_digest= and handoff_path= appear in stdout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/devtools/test_schema_commit_command.py` around lines 106 - 116,
Update the SchemaCommitResult stubs in this test file to include a handoff
value, then extend the JSON test assertions to verify both handoff and
handoff_path. Add equivalent non-JSON coverage asserting stdout contains
handoff_digest= and handoff_path= so the new operator-visible output path
executes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@polylogue/schemas/operator/commit.py`:
- Around line 155-158: Merge the duplicated generation.success handling in
commit.py by moving the handoff logic into the existing branch that already
creates registry_after, and reuse that registry_after instead of rebuilding
SchemaRegistry(storage_root=output_dir). Then remove the dry_run-only
suppression around handoff_path in the _commit_into flow so the path is reported
whenever the handoff file is written, leaving commit_provider_schema to enforce
the dry-run contract.
- Around line 66-79: Update the schema-inference receipt validation flow in
_accepted_gate_receipt_digest and its call site in SchemaCommitRequest handling
so the receipt is bound to the archive being committed. Pass
db_path=request.db_path into the digest check, then verify the receipt payload’s
archive identity fields (archive_root and input_paths.source_db) match the
requested database path before accepting a PASS verdict; keep the existing
schema and verdict validation unchanged for non-matching or missing bindings.
- Around line 165-168: Move existing handoff loading and validation ahead of the
generate_all_schemas call in the commit flow, while preserving the existing-file
conditional behavior. Store the validated receipt for reuse when calling
merged_with after generation, so any ValueError occurs before packages are
written and the final write_schema_inference_receipt still records the merged
handoff.

In `@polylogue/schemas/operator/receipt.py`:
- Line 405: Hash package.json only once in the receipt construction flow: update
the later version-files entry near the existing version_files initialization to
reuse the first hash_file(package_path) result instead of invoking hash_file
again. Preserve the current version_files contents and hash value.
- Around line 113-114: Remove the unused _canonical_payload helper from the
receipt schema module, since SchemaInferenceReceipt.receipt_digest already uses
hash_payload as the canonical digest path. Do not introduce an alternate
canonicalization flow; ensure imports used only by this helper are removed as
well.
- Around line 501-523: Update build_schema_inference_receipt to determine
whether the persisted packages contain any representable content, rather than
treating non-empty catalogs as sufficient. Reject providers whose elements are
all unsupported or nonrepresentable before constructing the committed
SchemaInferenceCoverageDecision, while preserving committed coverage and receipt
generation when representable content exists.

In `@tests/infra/inferred_corpus.py`:
- Around line 580-582: Update the campaign-mode entry paths in
tests/infra/inferred_corpus.py so they validate the handoff against the live
registry before proceeding: in the manifest-loading flow at lines 580-582,
require a registry when campaign_mode is enabled and call
_validate_inference_handoff before returning the manifest; in the
convergence-selection flow at lines 1015-1019, apply the same registry
validation before building selections, including the in-memory manifest path.
Keep _require_inference_handoff as the parsing guard, and make
_validate_inference_handoff the shared check on both paths.
- Around line 1234-1251: The _validate_inference_handoff flow must verify
receipt.gate_receipt_digest against the accepted gate digest before campaign
manifest compilation, rejecting mismatches. Update
tests/infra/inferred_corpus.py at lines 1234-1251 and
tests/unit/schemas/test_inferred_corpus_manifest.py at lines 121-123 to cover
this validation and ensure gate changes after merged_with() are not accepted.

In `@tests/unit/schemas/test_operator_commit.py`:
- Around line 44-49: Update the _gate_receipt helper to return the expected
schema_inference_gate_receipt_digest(payload) alongside the receipt path instead
of asserting truthiness. In test_new_provider_writes_catalog_and_element_files,
capture that expected digest and assert it equals
commit_result.handoff.gate_receipt_digest, verifying the commit records the
accepted receipt’s digest.
- Around line 166-169: Extend test_commit_requires_an_accepted_gate_receipt and
related tests to cover every rejection branch in _accepted_gate_receipt_digest:
a missing or unparsable receipt file, a receipt with schema different from
RECEIPT_SCHEMA, and a receipt whose verdict is not PASS. Build representative
receipt inputs in tmp_path, assert each is rejected with the existing
accepted-gate-receipt error, and specifically ensure a FAIL verdict cannot be
accepted.

---

Outside diff comments:
In `@devtools/schema_commit.py`:
- Around line 90-101: Wrap the commit_provider_schema call in the command’s
existing error-handling flow so ValueError from _accepted_gate_receipt_digest is
caught and rendered through the standard schema-commit: <message> output with
exit code 1. Preserve the --json error envelope behavior, and leave the separate
build_schema_privacy_config handling unchanged.

In `@tests/unit/devtools/test_schema_commit_command.py`:
- Around line 106-116: Update the SchemaCommitResult stubs in this test file to
include a handoff value, then extend the JSON test assertions to verify both
handoff and handoff_path. Add equivalent non-JSON coverage asserting stdout
contains handoff_digest= and handoff_path= so the new operator-visible output
path executes.

In `@tests/unit/schemas/test_operator_commit.py`:
- Around line 261-269: Extend the dry-run assertion block in
commit_provider_schema to also verify schema-inference-handoff.json is unchanged
on disk, using the same before/after byte comparison pattern already used for
catalog.json. Keep the focus on the existing commit_result, _request(...,
dry_run=True), and _read_element_schema checks, and add the handoff-file
comparison at the same level so the test confirms the staging redirect preserves
the persisted handoff.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 533a1975-cd5e-4574-b77f-2e73c0be00b7

📥 Commits

Reviewing files that changed from the base of the PR and between c9d127d and e97774c.

📒 Files selected for processing (10)
  • devtools/schema_commit.py
  • polylogue/maintenance/schema_inference_gate.py
  • polylogue/schemas/operator/commit.py
  • polylogue/schemas/operator/models.py
  • polylogue/schemas/operator/receipt.py
  • tests/infra/inferred_corpus.py
  • tests/unit/devtools/test_schema_commit_command.py
  • tests/unit/maintenance/test_schema_inference_gate.py
  • tests/unit/schemas/test_inferred_corpus_manifest.py
  • tests/unit/schemas/test_operator_commit.py

Comment thread polylogue/schemas/operator/commit.py Outdated
Comment thread polylogue/schemas/operator/commit.py Outdated
Comment thread polylogue/schemas/operator/commit.py Outdated
Comment thread polylogue/schemas/operator/receipt.py Outdated
Comment thread polylogue/schemas/operator/receipt.py Outdated
Comment thread polylogue/schemas/operator/receipt.py
Comment thread tests/infra/inferred_corpus.py
Comment thread tests/infra/inferred_corpus.py Outdated
Comment thread tests/unit/schemas/test_operator_commit.py
Comment thread tests/unit/schemas/test_operator_commit.py
Problem
Schema commit acceptance trusted a content digest over a minimal PASS payload, while receipt and inferred-corpus compilation maintained separate construct classifiers. The commit-to-manifest regression also used a provider without a persisted production wire format.

What changed
Validate the complete gate receipt contract, including archive authority identity, nonce and freshness, pristine query evidence, and explicit full BlobStore verification. Move construct classification into one canonical synthetic-runtime module used by both receipt and campaign compilation. Exercise the bundled registry relation annotations and the real chatgpt registry-to-manifest route, with mutation-sensitive rejection tests.

Compatibility/migration
Existing handoffs must be regenerated from the authoritative schema-inference gate. Unsupported and nonrepresentable entries remain explicit in package receipts and campaign manifests.
@Sinity Sinity changed the title feat: add schema inference receipt handoff fix: bind schema commits to pristine gate receipts Aug 5, 2026
Sinity added 3 commits August 5, 2026 16:55
Problem
The receipt freshness regression used a wall-clock timestamp tied to the work date, which could become stale before a later CI run.

What changed
Freeze the gate validator clock for the commit-route tests and anchor valid fixtures to the shared deterministic test instant. Keep the stale mutation case explicit.

Compatibility/migration
No runtime behavior changes.
Problem: Schema commit receipts could be treated as self-asserted after their hard-gate payload was mutated, and archive targeting inferred the root from db_path.parent. Campaign manifests could also outlive registry package or classifier changes and admit a provider with no executable witness.

What changed: Recompute the exact source-query, corpus-fidelity, ground-truth, blob-verification, schema-identity, and ArchiveLocation evidence at commit time. Bind commit requests to the configured archive root, revalidate live package hashes and classifier output, generate a real SyntheticCorpus witness, and reject all-unsupported campaigns. Add mutation-sensitive real-route coverage and preserve the CLI error envelope.

Compatibility/migration: Existing handoffs need a fresh gate receipt containing the new hard-gate evidence digest. Production commits still require an operator-generated PASS receipt for the configured archive and external ground-truth roots.
Problem: schema commits could validate a gate receipt for one archive while schema generation read an independently supplied index from another archive. Campaign manifests also accepted any syntactically valid handoff digest without proving it came from a fresh PASS receipt for the admitted archive.

What changed: schema commit resolves one active archive index and rejects mismatched db_path values before generation. Campaign admission now requires and validates the authoritative gate receipt against the supplied archive root, then compares its recomputed digest with the handoff. Real-route mutation-sensitive tests cover both attacks.

Compatibility/migration: non-campaign inferred-corpus fixtures may still construct handoffs with test digests; campaign callers must provide the authoritative gate receipt path and archive root.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/unit/devtools/test_schema_commit_command.py (1)

64-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that archive_root is forwarded.

_ConfigStub now carries archive_root, and main passes it into SchemaCommitRequest. This test pins every other forwarded field but not archive_root. That field is the new contract in this layer, so a regression that drops it stays undetected.

💚 Proposed added assertion
     assert request.db_path == tmp_path / "archive.db"
+    assert request.archive_root == tmp_path / "archive"
     assert request.full_corpus is True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/devtools/test_schema_commit_command.py` around lines 64 - 71,
Extend the assertions for the captured SchemaCommitRequest in this test to
verify that request.archive_root equals the _ConfigStub archive_root value,
alongside the existing forwarded-field checks. This ensures main preserves the
new archive_root contract when constructing the request.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@polylogue/maintenance/schema_inference_gate.py`:
- Around line 1182-1189: Wrap the _ground_truth_evidence call in the validator
with the same exception-to-ValueError conversion used by the surrounding
recomputation steps, covering its source.db and active-index read failures.
Preserve the existing evidence comparison and mismatch ValueError, while
ensuring OSError and sqlite3.Error from _ground_truth_evidence are exposed only
as the validator’s gate-refusal ValueError.
- Around line 1134-1146: Update the expected_input_paths validation near the
schema-inference gate to remove the self-derived receipt entry, or enforce it
against the caller-read receipt path rather than payload input. Ensure the
comparison no longer skips receipt while preserving validation of the configured
archive paths.
- Around line 1148-1227: Cache the recomputed evidence in the receipt-validation
flow covering _run_source_gates, _full_blob_hash_evidence,
_ground_truth_evidence, and verify_archive. Key the process-local cache by the
archive identity, generation, and sample_limit (and relevant root/index
context), reusing entries only for an exact match while preserving all existing
comparisons and anti-forgery checks. Invalidate or bypass cached evidence when
any key component changes.

In `@polylogue/schemas/synthetic/classification.py`:
- Around line 450-458: Align _SUPPORTED_SEMANTIC_ROLE_VALUES with the
message_container handling in the x-polylogue-semantic-role branch of the
synthetic classifier. Add message_container to the allowlist so object schemas
classified by the existing shortcut are consistently supported, preserving the
runtime’s normal-generation behavior for that role.

In `@tests/infra/inferred_corpus.py`:
- Around line 453-475: Update the campaign validation flow around
read_inferred_corpus_manifest and _validate_inference_handoff so persisted
manifest paths are not validated twice: rely on read_inferred_corpus_manifest’s
validation, while retaining validation for already-loaded manifests. Also
consolidate the duplicate witness generation between _compile_entry and
_validate_inference_handoff so the same spec and seed produce it only once.

In `@tests/unit/schemas/test_inferred_corpus_manifest.py`:
- Around line 39-49: Update the campaign tests around _authoritative_gate to use
the frozen_clock fixture and add the module-level frozen_clock_modules marker
for polylogue.maintenance.schema_inference_gate, matching the pattern in
test_operator_commit.py. Ensure the fixture is requested by each relevant test
or shared setup path so both receipt generation and validation use the frozen
clock.

---

Outside diff comments:
In `@tests/unit/devtools/test_schema_commit_command.py`:
- Around line 64-71: Extend the assertions for the captured SchemaCommitRequest
in this test to verify that request.archive_root equals the _ConfigStub
archive_root value, alongside the existing forwarded-field checks. This ensures
main preserves the new archive_root contract when constructing the request.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0c6a6e45-1a88-4140-9112-9c3fa7e9c6f4

📥 Commits

Reviewing files that changed from the base of the PR and between e97774c and 28e63fb.

📒 Files selected for processing (10)
  • devtools/schema_commit.py
  • polylogue/maintenance/schema_inference_gate.py
  • polylogue/schemas/operator/commit.py
  • polylogue/schemas/operator/models.py
  • polylogue/schemas/operator/receipt.py
  • polylogue/schemas/synthetic/classification.py
  • tests/infra/inferred_corpus.py
  • tests/unit/devtools/test_schema_commit_command.py
  • tests/unit/schemas/test_inferred_corpus_manifest.py
  • tests/unit/schemas/test_operator_commit.py

Comment thread polylogue/maintenance/schema_inference_gate.py
Comment on lines +1148 to +1227
try:
live_schema_identity = _tier_schema_identity(expected_root, location)
with open_readonly_connection(expected_root / ARCHIVE_TIER_SPECS[ArchiveTier.SOURCE].filename) as source:
referenced_hashes = _referenced_blob_hashes(source)
live_source_gates = _run_source_gates(
expected_root, index_path=location.active_index_path, sample_limit=sample_limit
)
live_query_results = _as_dict(live_source_gates.get("gates"))
live_duplicate_gate = live_source_gates.get("duplicate_gate")
if isinstance(live_duplicate_gate, Mapping):
live_query_results["zero-unexplained-byte-duplicates"] = dict(live_duplicate_gate)
live_full_blob = _full_blob_hash_evidence(expected_root, referenced_hashes=referenced_hashes)
except (OSError, sqlite3.Error, ValueError) as exc:
raise ValueError(f"unable to recompute schema-inference gate evidence: {exc}") from exc
if payload.get("schema_identity") != live_schema_identity or payload.get("source_schema_identity") != _as_dict(
_as_dict(live_schema_identity.get("tiers")).get("source")
):
raise ValueError("schema-inference gate receipt schema evidence is stale or mismatched")
if payload.get("query_results") != live_query_results:
raise ValueError("schema-inference gate receipt hard-gate query results changed")
if payload.get("source_denominators") != live_source_gates.get("source_counts", {}):
raise ValueError("schema-inference gate receipt source denominators changed")
if payload.get("blob_denominators") != live_source_gates.get("blob_denominators", {}):
raise ValueError("schema-inference gate receipt blob denominators changed")
recorded_ground_truth = payload.get("ground_truth_inputs")
recorded_origins = recorded_ground_truth.get("origins") if isinstance(recorded_ground_truth, Mapping) else None
live_ground_truth_roots: dict[str, tuple[Path, ...]] = {}
if isinstance(recorded_origins, Mapping):
for origin, raw_evidence in recorded_origins.items():
if not isinstance(origin, str) or not isinstance(raw_evidence, Mapping):
continue
raw_roots = raw_evidence.get("declared_roots")
if isinstance(raw_roots, list) and all(isinstance(path, str) for path in raw_roots):
live_ground_truth_roots[origin] = tuple(Path(path) for path in raw_roots)
live_ground_truth = _ground_truth_evidence(
expected_root,
index_path=location.active_index_path,
source_counts=cast(Mapping[str, Mapping[str, int]], live_source_gates.get("source_counts", {})),
roots=live_ground_truth_roots,
)
if payload.get("ground_truth_inputs") != live_ground_truth:
raise ValueError("schema-inference gate receipt ground-truth evidence changed")
try:
live_fidelity = _fidelity_evidence(
verify_archive(expected_root, checks=CORPUS_FIDELITY_CHECKS, sample_limit=sample_limit)
)
except Exception as exc:
raise ValueError(f"unable to recompute schema-inference corpus fidelity: {exc}") from exc
fidelity_keys = ("passed", "reasons", "typed_residuals", "denominators")
recorded_fidelity = payload.get("corpus_fidelity")
if not isinstance(recorded_fidelity, Mapping) or any(
recorded_fidelity.get(key) != live_fidelity.get(key) for key in fidelity_keys
):
raise ValueError("schema-inference gate receipt corpus-fidelity evidence changed")

full_blob = payload.get("full_blob_hash_verification")
if not isinstance(full_blob, Mapping) or full_blob.get("passed") is not True:
raise ValueError("schema-inference gate receipt lacks an explicit full_blob_hash_verification PASS")
verifier = full_blob.get("verifier")
if (
not isinstance(verifier, Mapping)
or verifier.get("identity") != "polylogue.storage.blob_store.BlobStore.verify_all"
):
raise ValueError("schema-inference gate receipt lacks the authoritative full blob verifier")
before = full_blob.get("before_snapshot")
after = full_blob.get("after_snapshot")
if (
not isinstance(before, Mapping)
or not isinstance(after, Mapping)
or not isinstance(before.get("digest"), str)
or before.get("digest") != after.get("digest")
or full_blob.get("failures") != []
or full_blob.get("missing_references") != []
or full_blob.get("errors") != []
):
raise ValueError("schema-inference gate receipt full blob verification evidence is incomplete")
if payload.get("hard_gate_evidence_digest") != schema_inference_hard_gate_evidence_digest(
live_query_results, live_full_blob
):
raise ValueError("schema-inference gate receipt hard-gate evidence digest does not match live evidence")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Count distinct call sites of the gate receipt validator to size the repeat-validation cost.
set -euo pipefail

rg -nP -C 4 '\bvalidate_schema_inference_gate_receipt\s*\(' --type=py

echo '--- boundaries that may validate more than once per command ---'
rg -nP -C 6 '_accepted_gate_receipt_digest|_validate_authoritative_gate_binding' --type=py

Repository: Sinity/polylogue

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo 'Tracked files around schema inference gate:'
git ls-files | rg 'polylogue/(maintenance/schema_inference_gate\.py|schemas/operator/commit\.py)$|schema_inference' || true

echo '--- locate validator name variants ---'
rg -n -C 3 'schema_inference.*receipt|GateReceipt|_accepted_gate_receipt_digest|_validate_authoritative_gate_binding|validate_.*receipt' --type=py || true

echo '--- locate archive identity / source gates helpers ---'
rg -n -C 3 '_tier_schema_identity|_run_source_gates|_full_blob_hash_evidence|_ground_truth_evidence|_fidelity_evidence|schema_inference_hard_gate_evidence_digest' --type=py || true

echo '--- outline relevant file (if present) ---'
if [ -f polylogue/maintenance/schema_inference_gate.py ]; then
  wc -l polylogue/maintenance/schema_inference_gate.py
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match validate_schema_inference_gate_receipt --view expanded || true
fi

Repository: Sinity/polylogue

Length of output: 868


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- locate validator name variants ---'
rg -n -C 3 'schema_inference.*receipt|GateReceipt|_accepted_gate_receipt_digest|_validate_authoritative_gate_binding|validate_.*receipt' --type=py || true

echo '--- locate archive identity / source gates helpers ---'
rg -n -C 3 '_tier_schema_identity|_run_source_gates|_full_blob_hash_evidence|_ground_truth_evidence|_fidelity_evidence|schema_inference_hard_gate_evidence_digest' --type=py || true

echo '--- outline relevant file ---'
if [ -f polylogue/maintenance/schema_inference_gate.py ]; then
  wc -l polylogue/maintenance/schema_inference_gate.py
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match validate_schema_inference_gate_receipt --view expanded || true
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match _accepted_gate_receipt_digest --view expanded || true
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match _validate_authoritative_gate_binding --view expanded || true
fi

echo '--- outline operator commit relevant file ---'
if [ -f polylogue/schemas/operator/commit.py ]; then
  wc -l polylogue/schemas/operator/commit.py
  ast-grep outline polylogue/schemas/operator/commit.py --view expanded || true
fi

Repository: Sinity/polylogue

Length of output: 827


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- locate validator name variants ---'
rg -n -C 3 'schema_inference.*receipt|GateReceipt|_accepted_gate_receipt_digest|_validate_authoritative_gate_binding|validate_.*receipt' --type=py || true

echo '--- locate archive identity / source gates helpers ---'
rg -n -C 3 '_tier_schema_identity|_run_source_gates|_full_blob_hash_evidence|_ground_truth_evidence|_fidelity_evidence|schema_inference_hard_gate_evidence_digest' --type=py || true

echo '--- outline relevant file ---'
if [ -f polylogue/maintenance/schema_inference_gate.py ]; then
  wc -l polylogue/maintenance/schema_inference_gate.py
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match validate_schema_inference_gate_receipt --view expanded || true
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match _accepted_gate_receipt_digest --view expanded || true
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match _validate_authoritative_gate_binding --view expanded || true
fi

echo '--- outline operator commit relevant file ---'
if [ -f polylogue/schemas/operator/commit.py ]; then
  wc -l polylogue/schemas/operator/commit.py
  ast-grep outline polylogue/schemas/operator/commit.py --view expanded || true
fi

Repository: Sinity/polylogue

Length of output: 827


🏁 Script executed:

#!/bin/bash
set -euо pipefail

echo '--- locate validator name variants ---'
rg -n -C 3 'schema_inference.*receipt|GateReceipt|_accepted_gate_receipt_digest|_validate_authoritative_gate_binding|validate_.*receipt' --type=py || true

echo '--- locate archive identity / source gates helpers ---'
rg -n -C 3 '_tier_schema_identity|_run_source_gates|_full_blob_hash_evidence|_ground_truth_evidence|_fidelity_evidence|schema_inference_hard_gate_evidence_digest' --type=py || true

echo '--- outline relevant file ---'
if [ -f polylogue/maintenance/schema_inference_gate.py ]; then
  wc -l polylogue/maintenance/schema_inference_gate.py
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match validate_schema_inference_gate_receipt --view expanded || true
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match _accepted_gate_receipt_digest --view expanded || true
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match _validate_authoritative_gate_binding --view expanded || true
fi

echo '--- outline operator commit relevant file ---'
if [ -f polylogue/schemas/operator/commit.py ]; then
  wc -l polylogue/schemas/operator/commit.py
  ast-grep outline polylogue/schemas/operator/commit.py --view expanded || true
fi

Repository: Sinity/polylogue

Length of output: 940


🏁 Script executed:

#!/bin/bash
set -u

echo '--- locate validator name variants ---'
rg -n -C 3 'schema_inference.*receipt|GateReceipt|_accepted_gate_receipt_digest|_validate_authoritative_gate_binding|validate_.*receipt' --type=py || true

echo '--- locate archive identity / source gates helpers ---'
rg -n -C 3 '_tier_schema_identity|_run_source_gates|_full_blob_hash_evidence|_ground_truth_evidence|_fidelity_evidence|schema_inference_hard_gate_evidence_digest' --type=py || true

echo '--- outline relevant files ---'
if [ -f polylogue/maintenance/schema_inference_gate.py ]; then
  wc -l polylogue/maintenance/schema_inference_gate.py
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match validate_schema_inference_gate_receipt --view expanded || true
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match _accepted_gate_receipt_digest --view expanded || true
  ast-grep outline polylogue/maintenance/schema_inference_gate.py --match _validate_authoritative_gate_binding --view expanded || true
fi

if [ -f polylogue/schemas/operator/commit.py ]; then
  wc -l polylogue/schemas/operator/commit.py
  ast-grep outline polylogue/schemas/operator/commit.py --view expanded || true
fi

echo '--- read relevant validator body around cited lines ---'
if [ -f polylogue/maintenance/schema_inference_gate.py ]; then
  sed -n '1130,1215p' polylogue/maintenance/schema_inference_gate.py | nl -ba -v 1130
fi

Repository: Sinity/polylogue

Length of output: 828


Avoid repeat full-archive recomputation on repeated receipt validation calls.

This re-runs _run_source_gates, _full_blob_hash_evidence, _ground_truth_evidence, and verify_archive on each call, including verify_archive over CORPUS_FIDELITY_CHECKS. _full_blob_hash_evidence snapshots the blob root twice, runs BlobStore.verify_all, and walks the store with iter_all. Recombution is needed for anti-forgery checks, but cache the recomputed evidence within the process so one command does not scan the archive multiple times for the same identity, generation, and sample_limit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@polylogue/maintenance/schema_inference_gate.py` around lines 1148 - 1227,
Cache the recomputed evidence in the receipt-validation flow covering
_run_source_gates, _full_blob_hash_evidence, _ground_truth_evidence, and
verify_archive. Key the process-local cache by the archive identity, generation,
and sample_limit (and relevant root/index context), reusing entries only for an
exact match while preserving all existing comparisons and anti-forgery checks.
Invalidate or bypass cached evidence when any key component changes.

Comment on lines +1182 to +1189
live_ground_truth = _ground_truth_evidence(
expected_root,
index_path=location.active_index_path,
source_counts=cast(Mapping[str, Mapping[str, int]], live_source_gates.get("source_counts", {})),
roots=live_ground_truth_roots,
)
if payload.get("ground_truth_inputs") != live_ground_truth:
raise ValueError("schema-inference gate receipt ground-truth evidence changed")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Wrap _ground_truth_evidence so the validator only raises ValueError.

Every other recomputation step converts read failures into ValueError. Lines 1148-1161 wrap the schema-identity, source-gate, and blob evidence calls. Lines 1190-1195 wrap the fidelity call. The _ground_truth_evidence call on lines 1182-1187 has no guard.

_ground_truth_evidence reads source.db and the active index; run_schema_inference_gate line 1309 catches OSError and sqlite3.Error from the same call. In the validator those exceptions escape unconverted. The commit boundary _accepted_gate_receipt_digest in polylogue/schemas/operator/commit.py and the campaign boundary _validate_authoritative_gate_binding in tests/infra/inferred_corpus.py both expect ValueError from this function, so a locked or unreadable tier surfaces a raw sqlite3.Error instead of a gate-refusal message.

🛡️ Proposed fix
-    live_ground_truth = _ground_truth_evidence(
-        expected_root,
-        index_path=location.active_index_path,
-        source_counts=cast(Mapping[str, Mapping[str, int]], live_source_gates.get("source_counts", {})),
-        roots=live_ground_truth_roots,
-    )
+    try:
+        live_ground_truth = _ground_truth_evidence(
+            expected_root,
+            index_path=location.active_index_path,
+            source_counts=cast(Mapping[str, Mapping[str, int]], live_source_gates.get("source_counts", {})),
+            roots=live_ground_truth_roots,
+        )
+    except (OSError, sqlite3.Error, ValueError) as exc:
+        raise ValueError(f"unable to recompute schema-inference ground-truth evidence: {exc}") from exc
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
live_ground_truth = _ground_truth_evidence(
expected_root,
index_path=location.active_index_path,
source_counts=cast(Mapping[str, Mapping[str, int]], live_source_gates.get("source_counts", {})),
roots=live_ground_truth_roots,
)
if payload.get("ground_truth_inputs") != live_ground_truth:
raise ValueError("schema-inference gate receipt ground-truth evidence changed")
try:
live_ground_truth = _ground_truth_evidence(
expected_root,
index_path=location.active_index_path,
source_counts=cast(Mapping[str, Mapping[str, int]], live_source_gates.get("source_counts", {})),
roots=live_ground_truth_roots,
)
except (OSError, sqlite3.Error, ValueError) as exc:
raise ValueError(f"unable to recompute schema-inference ground-truth evidence: {exc}") from exc
if payload.get("ground_truth_inputs") != live_ground_truth:
raise ValueError("schema-inference gate receipt ground-truth evidence changed")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@polylogue/maintenance/schema_inference_gate.py` around lines 1182 - 1189,
Wrap the _ground_truth_evidence call in the validator with the same
exception-to-ValueError conversion used by the surrounding recomputation steps,
covering its source.db and active-index read failures. Preserve the existing
evidence comparison and mismatch ValueError, while ensuring OSError and
sqlite3.Error from _ground_truth_evidence are exposed only as the validator’s
gate-refusal ValueError.

Comment on lines +450 to +458
if key == "x-polylogue-semantic-role":
role = value if isinstance(value, str) else None
if role == "message_timestamp":
return bool(schema_types & {"string", "number", "integer"}) and _annotation_supported(
key, value, root_schema
)
if role == "message_container":
return "object" in schema_types
return schema_type == "string" and _annotation_supported(key, value, root_schema)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find every producer and consumer of the message_container semantic role.
set -euo pipefail

rg -nP -C5 'message_container' --type=py

rg -nP -C5 'x-polylogue-semantic-role' --type=py polylogue
rg -nP -C4 '\bsemantic_role\b' --type=py polylogue

fd -t d 'synthetic' polylogue --exec ast-grep outline {} --items all

Repository: Sinity/polylogue

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "repo files sample"
git ls-files | sed -n '1,120p'

echo
echo "find classification.py"
fd -a 'classification.py$|inferred_corpus.py$|receipt.py$' . || true

echo
echo "search message_container"
rg -n -C5 'message_container' . || true

echo
echo "search semantic role support"
rg -n -C5 '_SUPPORTED_SEMANTIC_ROLE_VALUES|_unsupported_reason|_unsupported_for_package|message_container|message_timestamp|message_container' . || true

Repository: Sinity/polylogue

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "classification relevant section"
sed -n '1,210p' polylogue/schemas/synthetic/classification.py
echo
sed -n '420,470p' polylogue/schemas/synthetic/classification.py

echo
echo "semantic_values relevant section"
sed -n '1,160p' polylogue/schemas/synthetic/semantic_values.py

echo
echo "receipt relevant section"
sed -n '1,220p' polylogue/schemas/operator/receipt.py
echo
sed -n '220,340p' polylogue/schemas/operator/receipt.py

echo
echo "inferred_corpus relevant section"
rg -n -C8 '_unsupported_reason|unsupported|campaign|supported' tests/infra/inferred_corpus.py

echo
echo "tests around classification"
rg -n -C6 'unsupported_reason|_SUPPORTED_SEMANTIC_ROLE_VALUES|message_container|semantic-role|classification' tests polylogue/schemas/synthetic -g '*.py' || true

Repository: Sinity/polylogue

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "classification.py header and lines 140-170, 440-470"
sed -n '140,170p' polylogue/schemas/synthetic/classification.py
sed -n '440,470p' polylogue/schemas/synthetic/classification.py

echo
echo "_unsupported_reason references"
rg -n -C4 '_unsupported_reason\(' polylogue tests --type=py

echo
echo "semantic_values supported case"
sed -n '100,130p' polylogue/schemas/synthetic/semantic_values.py

echo
echo "pinning/semantic_models relevant"
sed -n '1,90p' polylogue/schemas/inference/semantic/models.py
sed -n '18,32p' polylogue/schemas/inference/semantic/runtime.py

Repository: Sinity/polylogue

Length of output: 7834


Align the synthetic allowlist with message_container support.

semantics.py and the injector list message_container as an allowed semantic role, but _SUPPORTED_SEMANTIC_ROLE_VALUES excludes it. Object nodes with x-polylogue-semantic-role: message_container may be classified supported by the shortcut on lines 456-457, while the synthetic runtime only defers to normal generation for that role. Add message_container to _SUPPORTED_SEMANTIC_ROLE_VALUES if the schema is acceptable, or remove the shortcut so unsupported annotations are rejected consistently.
[maintenance_and_code_quality]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@polylogue/schemas/synthetic/classification.py` around lines 450 - 458, Align
_SUPPORTED_SEMANTIC_ROLE_VALUES with the message_container handling in the
x-polylogue-semantic-role branch of the synthetic classifier. Add
message_container to the allowlist so object schemas classified by the existing
shortcut are consistently supported, preserving the runtime’s normal-generation
behavior for that role.

Comment on lines +453 to +475
persisted_manifest = (
read_inferred_corpus_manifest(
manifest,
campaign_mode=campaign_mode,
registry=registry,
gate_receipt_path=gate_receipt_path,
archive_root=archive_root,
)
if isinstance(manifest, Path)
else manifest
)
if campaign_mode:
_require_inference_handoff(persisted_manifest)
if registry is None:
raise ValueError("campaign mode requires a live schema registry")
providers = tuple(sorted({entry.key.provider for entry in persisted_manifest.entries}))
_validate_inference_handoff(
persisted_manifest,
registry,
providers=providers,
gate_receipt_path=gate_receipt_path,
archive_root=archive_root,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Campaign validation runs twice for a persisted manifest path.

When manifest is a Path, lines 453-463 call read_inferred_corpus_manifest with campaign_mode=True. That call already runs _require_inference_handoff and _validate_inference_handoff. Lines 464-475 then run both again on the same manifest.

_validate_inference_handoff is not cheap. It calls _validate_authoritative_gate_binding, which calls validate_schema_inference_gate_receipt. That validator recomputes the full live archive evidence: source-gate queries, verify_archive, and BlobStore.verify_all. It also generates a synthetic corpus witness for every executable entry. Running the whole set twice doubles the cost of the campaign path for no added guarantee.

Validate only the branch that was not already validated.

♻️ Proposed single validation
-    persisted_manifest = (
-        read_inferred_corpus_manifest(
-            manifest,
-            campaign_mode=campaign_mode,
-            registry=registry,
-            gate_receipt_path=gate_receipt_path,
-            archive_root=archive_root,
-        )
-        if isinstance(manifest, Path)
-        else manifest
-    )
-    if campaign_mode:
+    already_validated = isinstance(manifest, Path)
+    persisted_manifest = (
+        read_inferred_corpus_manifest(
+            manifest,
+            campaign_mode=campaign_mode,
+            registry=registry,
+            gate_receipt_path=gate_receipt_path,
+            archive_root=archive_root,
+        )
+        if already_validated
+        else manifest
+    )
+    if campaign_mode and not already_validated:
         _require_inference_handoff(persisted_manifest)

A related duplication exists on the compile path: _compile_entry generates a witness on line 618, and _validate_inference_handoff generates the same witness with the same spec and seed on line 779. Consider keeping the witness in one place.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
persisted_manifest = (
read_inferred_corpus_manifest(
manifest,
campaign_mode=campaign_mode,
registry=registry,
gate_receipt_path=gate_receipt_path,
archive_root=archive_root,
)
if isinstance(manifest, Path)
else manifest
)
if campaign_mode:
_require_inference_handoff(persisted_manifest)
if registry is None:
raise ValueError("campaign mode requires a live schema registry")
providers = tuple(sorted({entry.key.provider for entry in persisted_manifest.entries}))
_validate_inference_handoff(
persisted_manifest,
registry,
providers=providers,
gate_receipt_path=gate_receipt_path,
archive_root=archive_root,
)
already_validated = isinstance(manifest, Path)
persisted_manifest = (
read_inferred_corpus_manifest(
manifest,
campaign_mode=campaign_mode,
registry=registry,
gate_receipt_path=gate_receipt_path,
archive_root=archive_root,
)
if already_validated
else manifest
)
if campaign_mode and not already_validated:
_require_inference_handoff(persisted_manifest)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/infra/inferred_corpus.py` around lines 453 - 475, Update the campaign
validation flow around read_inferred_corpus_manifest and
_validate_inference_handoff so persisted manifest paths are not validated twice:
rely on read_inferred_corpus_manifest’s validation, while retaining validation
for already-loaded manifests. Also consolidate the duplicate witness generation
between _compile_entry and _validate_inference_handoff so the same spec and seed
produce it only once.

Comment on lines +39 to +49
def _authoritative_gate(tmp_path: Path) -> tuple[Path, Path, str]:
archive_root = tmp_path / "archive"
receipt_path = tmp_path / "schema-inference-gate-receipt.json"
_seed_archive(archive_root)
result = run_schema_inference_gate(
archive_root,
receipt_path=receipt_path,
ground_truth_roots={"codex-session": (tmp_path / "archive-codex-ground-truth",)},
)
assert result.passed
return archive_root, receipt_path, schema_inference_gate_receipt_digest(result.payload)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Freeze the gate clock in the campaign tests.

_authoritative_gate calls run_schema_inference_gate, which stamps generated_at with datetime.now(UTC). Every campaign test then reaches validate_schema_inference_gate_receipt, which reads datetime.now(UTC) again and rejects the receipt outside the RECEIPT_MAX_AGE_SECONDS window. These tests are therefore timestamp-sensitive, they read the real clock indirectly, and they carry no uses_real_clock marker. A slow run that crosses the freshness window makes them fail.

tests/unit/schemas/test_operator_commit.py already solves this. It marks frozen_clock_modules("polylogue.maintenance.schema_inference_gate") and requests the frozen_clock fixture. Apply the same pattern here.

As per coding guidelines: "Timestamp-sensitive tests must use frozen_clock; direct datetime.now and time.time reads are prohibited unless explicitly marked with uses_real_clock."

💚 Proposed fixture
+from tests.infra.frozen_clock import FrozenClock
+
+
+@pytest.fixture(autouse=True)
+def _freeze_gate_clock(frozen_clock: FrozenClock) -> None:
+    """Keep gate receipt freshness deterministic across the campaign tests."""
+
+
 def _authoritative_gate(tmp_path: Path) -> tuple[Path, Path, str]:

Add the module marker so the gate module resolves the frozen clock:

pytestmark = pytest.mark.frozen_clock_modules("polylogue.maintenance.schema_inference_gate")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/schemas/test_inferred_corpus_manifest.py` around lines 39 - 49,
Update the campaign tests around _authoritative_gate to use the frozen_clock
fixture and add the module-level frozen_clock_modules marker for
polylogue.maintenance.schema_inference_gate, matching the pattern in
test_operator_commit.py. Ensure the fixture is requested by each relevant test
or shared setup path so both receipt generation and validation use the frozen
clock.

Source: Coding guidelines

Problem: the authoritative receipt exposed ground-truth fidelity denominators, but validator admission did not compare that top-level field with live corpus evidence. A tampered field could therefore be covered by a recomputed handoff digest.

What changed: validator admission now compares ground_truth_denominators exactly with live corpus-fidelity denominators. The campaign test admits the valid receipt, tampers only that field, recomputes the receipt digest, and asserts rejection.

Compatibility/migration: valid receipts retain the existing contract and all prior archive, index, freshness, blob, query, ground-truth, corpus-fidelity, and persisted-resume checks.

Co-Authored-By: Claude <noreply@anthropic.com>
Sinity added 2 commits August 6, 2026 02:16
Problem: the schema receipt handoff branch diverged while current master gained independent gate, archive-identity, and attestation hardening.

What changed: reconcile the handoff implementation with current master, unify path and in-memory gate receipt validation, retain authoritative source and user attestations, preserve the complete archive identity and source snapshot evidence, and order unsupported corpus decisions so element and schema defects keep their specific typed reasons.

Verification: devtools test tests/unit/devtools/test_schema_commit_command.py tests/unit/maintenance/test_schema_inference_gate.py tests/unit/schemas/test_operator_commit.py tests/unit/schemas/test_inferred_corpus_manifest.py (92 passed).
Problem: the unified path-or-payload receipt validator returned a union at two statically distinct call sites.

What changed: narrow the path-based authorization result and the in-memory handoff digest at their call boundaries.

Verification: the pre-push quick verification is being rerun after this correction.
@Sinity
Sinity merged commit 592bd9d into master Aug 6, 2026
2 of 3 checks passed
@Sinity
Sinity deleted the feature/feat/schema-inference-receipt-handoff branch August 6, 2026 00:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/unit/devtools/test_schema_commit_command.py (1)

70-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the CLI forwards archive_root.

_ConfigStub now carries archive_root, and devtools/schema_commit.py passes config.archive_root into SchemaCommitRequest. The commit gate binds the receipt to that archive root, so the forwarding is security-relevant. This test asserts db_path and schema_inference_gate_receipt_path but not archive_root. Add the assertion so a dropped or wrong archive root fails here.

💚 Proposed assertion
     assert request.db_path == tmp_path / "archive.db"
+    assert request.archive_root == tmp_path / "archive"
     assert request.full_corpus is True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/devtools/test_schema_commit_command.py` around lines 70 - 77,
Extend the captured request assertions in the schema commit CLI test to verify
that archive_root is forwarded from _ConfigStub unchanged, alongside the
existing db_path and schema_inference_gate_receipt_path checks. Use the expected
temporary archive-root value configured by the test.
polylogue/maintenance/schema_inference_gate.py (1)

2108-2113: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the duplicated __all__ entry.

"schema_inference_gate_receipt_digest" is listed on line 2108 and again on line 2113.

♻️ Proposed fix
     "schema_inference_quiescence",
-    "schema_inference_gate_receipt_digest",
     "validate_schema_inference_gate_receipt",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@polylogue/maintenance/schema_inference_gate.py` around lines 2108 - 2113,
Remove the duplicate "schema_inference_gate_receipt_digest" entry from the
__all__ list, keeping a single occurrence alongside the related schema inference
symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@polylogue/maintenance/schema_inference_gate.py`:
- Around line 1893-1908: Update validate_schema_inference_gate_receipt with
typing.overload declarations that return dict[str, object] for Mapping payloads
and str for Path inputs, while preserving the existing dispatcher
implementation. Remove the corresponding cast wrappers at the schema-generation
call site and in _accepted_gate_receipt_digest, relying on the overload-resolved
return types.

---

Outside diff comments:
In `@polylogue/maintenance/schema_inference_gate.py`:
- Around line 2108-2113: Remove the duplicate
"schema_inference_gate_receipt_digest" entry from the __all__ list, keeping a
single occurrence alongside the related schema inference symbols.

In `@tests/unit/devtools/test_schema_commit_command.py`:
- Around line 70-77: Extend the captured request assertions in the schema commit
CLI test to verify that archive_root is forwarded from _ConfigStub unchanged,
alongside the existing db_path and schema_inference_gate_receipt_path checks.
Use the expected temporary archive-root value configured by the test.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 916cec90-4279-46af-9711-fe542d09ce64

📥 Commits

Reviewing files that changed from the base of the PR and between 28e63fb and 88fd939.

📒 Files selected for processing (7)
  • polylogue/maintenance/schema_inference_gate.py
  • polylogue/schemas/operator/commit.py
  • polylogue/schemas/operator/receipt.py
  • tests/infra/inferred_corpus.py
  • tests/unit/devtools/test_schema_commit_command.py
  • tests/unit/maintenance/test_schema_inference_gate.py
  • tests/unit/schemas/test_inferred_corpus_manifest.py

Comment on lines +1893 to +1908
def validate_schema_inference_gate_receipt(
payload_or_path: Mapping[str, object] | Path,
*,
archive_root: Path,
now: datetime | None = None,
) -> str | dict[str, object]:
"""Validate either an in-memory gate payload or a receipt path.

Schema commit and inferred-corpus callers already hold the parsed payload,
while the CLI schema-generation route owns a receipt path. Both routes use
the same authoritative validation contract.
"""

if isinstance(payload_or_path, Path):
return _validate_schema_inference_gate_path(payload_or_path, archive_root=archive_root, now=now)
return _validate_schema_inference_gate_payload(payload_or_path, archive_root=archive_root, now=now)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow the dispatcher return type with @overload.

validate_schema_inference_gate_receipt returns str | dict[str, object]. Both call sites must then discard the union with cast: line 1916 casts to dict[str, object], and _accepted_gate_receipt_digest in polylogue/schemas/operator/commit.py casts to str. A wrong cast type-checks silently at this privileged boundary. Declare overloads so each route returns its exact type and the casts become unnecessary.

♻️ Proposed overloads
+@overload
+def validate_schema_inference_gate_receipt(
+    payload_or_path: Path, *, archive_root: Path, now: datetime | None = None
+) -> dict[str, object]: ...
+
+
+@overload
+def validate_schema_inference_gate_receipt(
+    payload_or_path: Mapping[str, object], *, archive_root: Path, now: datetime | None = None
+) -> str: ...
+
+
 def validate_schema_inference_gate_receipt(
     payload_or_path: Mapping[str, object] | Path,
     *,
     archive_root: Path,
     now: datetime | None = None,
 ) -> str | dict[str, object]:

Import overload from typing on line 25, then drop the cast wrappers at line 1916 and in polylogue/schemas/operator/commit.py.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@polylogue/maintenance/schema_inference_gate.py` around lines 1893 - 1908,
Update validate_schema_inference_gate_receipt with typing.overload declarations
that return dict[str, object] for Mapping payloads and str for Path inputs,
while preserving the existing dispatcher implementation. Remove the
corresponding cast wrappers at the schema-generation call site and in
_accepted_gate_receipt_digest, relying on the overload-resolved return types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant