fix: bind schema commits to pristine gate receipts - #3815
Conversation
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.
📝 WalkthroughWalkthroughThe 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. ChangesSchema inference receipt workflow
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 winAssert that the dry run leaves the persisted handoff untouched.
_commit_intocallswrite_schema_inference_receiptunconditionally. The dry run is safe only becausecommit_provider_schemaredirectsoutput_dirto a temporary staging root. This test proves that invariant forcatalog.jsonand the element schema, but not forschema-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 winHandle the new
ValueErrorfrom gate receipt validation.
_accepted_gate_receipt_digestraisesValueErrorwhen the receipt is unreadable, uses the wrong schema, or has a non-PASSverdict.commit_provider_schemaon line 90 is not inside atryblock. Line 80 wraps onlybuild_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 winCover the new handoff output.
Every
SchemaCommitResultstub in this file omitshandoff, so it defaults toNone. The new print block atdevtools/schema_commit.pylines 117-120 therefore never runs in any test. Thehandoff_digestandhandoff_pathlines are operator-visible output with no coverage.The JSON test is the natural place to pin this, because
to_dictnow emitshandoffandhandoff_path. Populatehandoffin 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=andhandoff_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
📒 Files selected for processing (10)
devtools/schema_commit.pypolylogue/maintenance/schema_inference_gate.pypolylogue/schemas/operator/commit.pypolylogue/schemas/operator/models.pypolylogue/schemas/operator/receipt.pytests/infra/inferred_corpus.pytests/unit/devtools/test_schema_commit_command.pytests/unit/maintenance/test_schema_inference_gate.pytests/unit/schemas/test_inferred_corpus_manifest.pytests/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.
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.
There was a problem hiding this comment.
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 winAssert that
archive_rootis forwarded.
_ConfigStubnow carriesarchive_root, andmainpasses it intoSchemaCommitRequest. This test pins every other forwarded field but notarchive_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
📒 Files selected for processing (10)
devtools/schema_commit.pypolylogue/maintenance/schema_inference_gate.pypolylogue/schemas/operator/commit.pypolylogue/schemas/operator/models.pypolylogue/schemas/operator/receipt.pypolylogue/schemas/synthetic/classification.pytests/infra/inferred_corpus.pytests/unit/devtools/test_schema_commit_command.pytests/unit/schemas/test_inferred_corpus_manifest.pytests/unit/schemas/test_operator_commit.py
| 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") |
There was a problem hiding this comment.
🚀 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=pyRepository: 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
fiRepository: 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
fiRepository: 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
fiRepository: 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
fiRepository: 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
fiRepository: 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.
| 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") |
There was a problem hiding this comment.
🩺 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.
| 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.
| 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) |
There was a problem hiding this comment.
🎯 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 allRepository: 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' . || trueRepository: 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' || trueRepository: 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.pyRepository: 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.
| 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, | ||
| ) |
There was a problem hiding this comment.
🚀 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.
| 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.
| 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) |
There was a problem hiding this comment.
📐 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>
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.
There was a problem hiding this comment.
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 winAssert that the CLI forwards
archive_root.
_ConfigStubnow carriesarchive_root, anddevtools/schema_commit.pypassesconfig.archive_rootintoSchemaCommitRequest. The commit gate binds the receipt to that archive root, so the forwarding is security-relevant. This test assertsdb_pathandschema_inference_gate_receipt_pathbut notarchive_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 winRemove 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
📒 Files selected for processing (7)
polylogue/maintenance/schema_inference_gate.pypolylogue/schemas/operator/commit.pypolylogue/schemas/operator/receipt.pytests/infra/inferred_corpus.pytests/unit/devtools/test_schema_commit_command.pytests/unit/maintenance/test_schema_inference_gate.pytests/unit/schemas/test_inferred_corpus_manifest.py
| 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) |
There was a problem hiding this comment.
📐 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.
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
hard_gate_evidence_digestover 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.archive_roottoSchemaCommitRequest. Existing handoffs are checked against the accepted gate before generation, and stale gate or handoff combinations fail before package writes.SyntheticCorpuswitness for each executable manifest entry and rejected campaigns with no executable synthetic corpus selection.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.pydirenv exec . devtools verify --quick0e38656f1, 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-gatefor 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
Bug Fixes