Release 1.4.0 — federation reads + layering decouple + policy-boundary & starlette security fixes#24
Conversation
read_floor() now gates on verify_integrity() before returning the floor, so a raw-DB-written/forged tail record that breaks the keyless hash chain can no longer silently set the routing floor (it maps to the fail-closed structured default). Cryptographic operator_sig verification stays the operator-side doctor check (KEY_RESET-acknowledgment path only); a recomputed-chain forgery remains the conceded raw-file-write residual (README.md:137). Retires test_read_floor_uses_tail_read's 'must not call read_all' premise (the integrity gate supersedes it) and corrects the now-false module docstring. Closes the load-bearing half of legis-476ab6f125 (PRD-0005 criterion 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
read_floor's keyless integrity gate cannot detect a file-write attacker who recomputes the keyless chain; on a non-rekeyed ledger that is caught by neither the hot read nor doctor (doctor's operator_sig check is the KEY_RESET-ack path only). It is the conceded raw-file-write residual (README.md:137). This characterization test makes the limit visible in the suite so it is never implied-closed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…→Plainweave refs Adds/normalizes the 'not-for-X' Banner naming this member's specific misuse (deconfliction-first, not security/compliance); fixes hardcoded Charter→Plainweave prose. Re-vendored kit; build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A protected record appended inside a batch defers its HeadAnchor advance (the mid-batch head read is forbidden, Q-M5) and, unlike SignoffGate, had no transaction owner to re-advance it after commit — so a batched protected append could leave the anchor at the pre-batch head and a later tail-truncation would go undetected. Add ProtectedGate.transaction(), a verbatim mirror of SignoffGate.transaction(), as that owner. Closes legis-0c310712a7 (PRD-0005 criterion 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A protected append inside a raw store.transaction() (bypassing gate.transaction()) leaves the anchor stale — the supported safe path is gate.transaction(). This characterization test makes the parity limit visible so it is never implied-closed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final-review cleanups (M1 unused Verdict import; M2 rank-5 comment landed inside the _truncate_to docstring). No logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r injectable invoke seam Rewrites the warpline preflight client from an HTTP layer to a lightweight MCP-envelope consumer. Deletes HttpWarplineClient and all urllib/http.client/ ipaddress helpers; adds WarplineMcpClient with an injectable Invoke seam for offline testing. Validates schema, ok, meta (isinstance guard closes GV-LG-3 non-dict escape), local_only, peer_side_effects, and data.completeness — every contract fault fails closed to WarplineError. Envelope is passed through verbatim (bare-object MCP output schema makes pass-through lossless). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds StdioMcpInvoke — the production Invoke: a one-shot stdio JSON-RPC call to warpline-mcp. shell=False + list argv; text=False byte-bounded stdout; empty-argv rejected; entire post-spawn parse wrapped so every fault (spawn, timeout, non-JSON, scalar result, isError, oversize) → WarplineError (fail closed). Adds _read_jsonrpc_result for line-by-line id-matched response scanning. Adds test_stdio_invoke.py (11 tests: fake server round-trip, live transcript replay DoD gate, 5 fault parametrize, missing binary, empty argv, timeout, oversize). Live-capture gate (warpline-mcp 1.2.0): exits on stdin-EOF (rc=0), no interleaved I/O required, protocolVersion 2025-03-26, structuredContent dict returned — subprocess.run one-shot confirmed safe. notifications/initialized → id:null error, correctly ignored (id != 2). Fixture warpline-mcp-live-session.jsonl is the verbatim 3-line capture. Concern flagged (not fixed): warpline_impact_radius_get requires a "repo" arg that WarplineMcpClient._call does not yet supply. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… fix cross-task import breaks Replace WARPLINE_API_URL/HttpWarplineClient with WARPLINE_MCP_CMD/WarplineMcpClient in build_runtime; supply repo=str(project_root()) from the established cwd-anchored resolver. Add required repo: str param to WarplineMcpClient.__init__ and thread it into _call arguments (live capture proved warpline_impact_radius_get/-reverify_worklist_get require "repo"; without it JSON-RPC -32602 kills the seam). Update all 9 test_client.py construction sites to pass repo="/tmp/r" and tighten the argument-shape assertion. Fix the three test_server.py tests that referenced the deleted HttpWarplineClient and WARPLINE_API_URL. After this commit: mypy src/legis is clean, pytest collects 1285 tests with only test_warpline_preflight_oracle.py failing (Task 4's scope). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ular oracle; reverse producer obligation Replace the hand-rolled flat golden with real warpline.impact_radius.v1 / warpline.reverify_worklist.v1 envelopes live-captured from warpline-mcp 1.2.0 (legis repo, HEAD~1..HEAD, 2026-06-27). Add machine-readable provenance marker (_provenance.source = "live-captured") with a CI-visible test that fails if "pending-live-capture" is committed without the escape env var. Rewrite the oracle: deleted HttpWarplineClient/_decode_json_response imports removed; all assertions now flow the golden through WarplineMcpClient._call (schema/ok/meta/completeness validation is real), asserting HARDCODED literals (schema=="warpline.impact_radius.v1", completeness=="NO_SNAPSHOT", affected==[], meta.local_only==True, etc.) — never a re-parse of the golden. Layer-2 recheck repointed from the obsolete REST path to the MCP contract fixture path. PROVENANCE.md: delete the "WARPLINE PRODUCER-SIDE OBLIGATION" block; record legis as consumer of warpline's extant envelope per SEAM 4 §4A + GV-LG-3. Re-pin GOLDEN_BLOB_SHA = 777b858. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…shaped envelopes + GV-LG-3 positive test
Replace all flat {affected/entries, count} warpline fakes with envelopes carrying
schema/ok/data.completeness/meta so the advisory payload (hostile values in
data.affected/data.items), not a contract violation, is what's proven inert.
test_warpline_advisory_boundary.py:
- _HostileWarpline now returns real-shaped envelopes with GV-LG-3-valid meta
(local_only:true, peer_side_effects:[]) and hostile advisory payload (SEI="EVERYTHING").
- Add guard in byte-identity test: assert hostile side reached status=="checked"
(side assertion, outside the compared blobs) so vacuous unavailable==unavailable
cannot silently pass the test.
- Add test_gv_lg_3_invalid_meta_peer_side_effects_yields_unavailable: routes
through WarplineMcpClient with peer_side_effects=["some-peer"] and asserts
the end-to-end result is status=="unavailable".
- Structural boundary test (lines 143-174, derived from _TOOL_HANDLERS) preserved unchanged.
test_output_schema_conformance.py:
- _FakeWarpline returns real-shaped envelopes with valid meta (the old flat stubs
would have been refused → unavailable, silently flipping status to unavailable
and making the checked assertion vacuous).
test_server.py + test_preflight.py:
- All remaining flat warpline fakes updated to real-shaped envelopes;
assertions updated to match (pass-through semantics preserved).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Measured coverage is 91.9% (79/86 statements covered in client.py). Floor set at 88% to lock in the gain from the MCP-client rewrite while leaving headroom for incidental churn. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… content-fallback test IMPORTANT #1: add warpline-mcp-reverify-session.jsonl — three-line JSON-RPC transcript of a real warpline_reverify_worklist_get call (warpline-mcp 1.2.0, 2026-06-27, legis repo, HEAD~1..HEAD). Adds test_replays_a_REAL_reverify_session that echoes these bytes through StdioMcpInvoke and asserts schema/completeness/ items from the capture. Golden's reverify_worklist was already byte-identical to the freshly captured structuredContent; GOLDEN_BLOB_SHA unchanged. Updates PROVENANCE.md to document both halves are now live-captured with committed transcripts. MINOR (b): make text=False explicit in StdioMcpInvoke's subprocess.run call (was implicit via the absence of text=True; explicit kwarg prevents a future refactor from silently switching to char mode). MINOR (c): add test_content_text_fallback_parse — exercises the content[0].text parse path in StdioMcpInvoke when structuredContent is absent from the result. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… ship warpline preflight MCP-envelope fix; PDR-0005, PDR-0006 - PDR-0005: accept legis-476ab6f125 + legis-0c310712a7 against PRD-0005 (north-star 3->1; legis-0186c23a2c remains) - PDR-0006: warpline preflight conforms to warpline's extant MCP envelope (transport=MCP, owner-confirmed); producer-obligation reversed; reverify kept droppable pending wardline - refresh metrics.md (north-star 1; advisory-boundary re-proven; live-Loomweave gate -> skip-not-fail; coverage 92.25%), roadmap.md (Now 2/3 done), current-state.md - includes the 3 codebase-validated implementation plans + review history Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the warpline producer-obligation framing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…; sync uv.lock to 1.3.0 Plan for the warpline→legis governance read seam, revised against the 7-agent ultracode review (CHANGES_REQUESTED → all 8 must-fixes folded in). Contract artifacts (schema + warpline prompt) staged uncommitted for the build's Task 1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Commits the FROZEN governance_read.v1 contract: the discriminated-union JSON schema (contracts/governance_read.v1.schema.json), the warpline hand-off prompt (docs/contracts/warpline-governance-read.v1-prompt.md), and the pinning test suite (tests/contract/test_governance_read_v1_schema.py). Tests cover 3 positives, 3 discriminator-negatives, 8 constraint-negatives, 1 format test (with load-bearing rfc3339-validator assertion), and 1 drift guard (structural equality of prompt's embedded schema block vs committed file, stripping non-validating descriptions only). Adds jsonschema[format] to the dev group so Draft202012Validator format_checker enforces the RFC3339 date-time constraint on as_of. .v1 is a ONE-WAY DOOR — any change to validation logic requires .v2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… helper Adds the per-SEI governance-clearance projection (`read_governance_for_sei`), the CLI/batch verified-gate wrapper (`read_governance_for_sei_gate` — mirrors `evaluate_override_rate_gate` so both verify halves are mandatory), and the shared unavailable-envelope helper (`governance_read_unavailable`) to `service/governance.py`. Exports all three from `service/__init__.py`. Includes `_POSTURE_BY_KIND` and `_is_rfc3339`; unknown-kind / bad-as_of records are omitted (asymmetric-error), not fabricated. TDD red→green: 10 tests in `tests/service/test_governance_read.py`, mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… projection) Adds the `governance_read` MCP tool that exposes per-SEI verified governance clearances as the `governance_read.v1` envelope, completing the warpline seam for advisory governance enrichment. Key properties: - BOTH-objects fail-closed pre-gate (mirrors attestation_get): gates on BOTH runtime.protected_gate AND runtime.trail_verifier; missing either -> status:"unavailable" (discriminated), never silent checked/[]. - Handler calls read_governance_for_sei(_governance_trail_records(runtime)) which runs BOTH verify_integrity (chain) AND TrailVerifier.verify (sigs) via the verified_records path; tampered trail -> AUDIT_INTEGRITY_FAILURE. - outputSchema built via _one_of over the v1 clearance_record fields (sei/disposition/posture/authority/as_of/reasons/content_hash), NOT attestation_get's fields. - Registered in _AGENT_TOOLS, tool_definitions(), and _TOOL_HANDLERS. Tests: - tests/mcp/test_governance_read_tool.py: handler tests (checked/unavailable/ tamper) incl. BOTH-objects gate on each missing half. - tests/mcp/test_output_schema_conformance.py: two explicit variant cases (checked + unavailable) validate against the declared _one_of outputSchema. - tests/conformance/test_governance_read_oracle.py: FROZEN GOLDEN mirroring test_warpline_attestation_oracle.py; byte-pinned blob SHA + LIVE==FROZEN value assert (not schema.validate); both clearance kinds pinned; content_hash cross-check confirms same records as the attestation golden. - tests/conformance/fixtures/legis-governance-read.golden.json: committed golden (blob SHA 898fd1a). - tests/mcp/test_server.py: update hard-coded tool list to include governance_read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exposes the governance_read.v1 per-SEI clearance read on the HTTP adapter
(Task 4). The route gates on BOTH protected_gate and trail_verifier (fail-closed
pre-gate identical to the MCP tool); verified_governance_records() runs both
chain and signature checks, mapping tamper to HTTP 500. Missing either object
yields a discriminated status:unavailable envelope, never a silent checked/[].
{sei:path} captures slash-bearing SEIs correctly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exposes per-SEI verified governance clearances (governance_read.v1) at the CLI boundary. Both verification halves are mandatory per Constraint 1: verify_integrity() (hash-chain/seq-contiguity/delete-reorder defence) runs FIRST, then read_governance_for_sei_gate (signature half via the service layer). Omitting either half is a false-green — test_cli_governance_read_chain_tamper_exits_nonzero is RED without verify_integrity() and GREEN with it (mutation-verified). Fail-closed: no LEGIS_HMAC_KEY → unavailable; missing DB → unavailable; chain tamper → exit 1 + "audit integrity" stderr; signature tamper (key mismatch) → exit 1. No bare except. No --json flag (output is always JSON). No duplicate governance logic in the adapter — all decisions flow through service/governance.py. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…" contract
All tamper paths (chain gap AND signature mismatch) now emit the "audit
integrity" substring on stderr, matching the established contract used by
`service/governance.py:verified_records`, `mcp.py`, and the chain-tamper
path already in the same CLI helper. Fixes case (d) in the test, which
previously asserted "verification failed" (a substring only of the service
gate's AuditIntegrityError message, not the contract marker).
Change: `_governance_read` except block now prints
`"Error: audit integrity: {exc}"` instead of `"Error: {exc}"`, so the
"audit integrity" substring is present regardless of which verification
half raised. Test case (d) assertion updated from `"verification failed"`
to `"audit integrity"`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tructural check Adds read_governance_for_sei, read_governance_for_sei_gate, and governance_read_unavailable to the explicit warpline-free symbol list in test_runtime_warpline_referenced_in_no_verdict_path_function, so any future accidental warpline reference in these service functions is caught immediately. Full CI-equivalent sweep passes: coverage 92.39%/all per- package floors hold; ruff/mypy clean; SEI oracle, policy-boundary-check, governance-gate all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ance_read.v1 Adds three test_cross_transport_* tests to the contract freeze file, each capturing a REAL output from one transport adapter (MCP golden / HTTP TestClient / CLI main()) and validating it against the committed frozen contracts/governance_read.v1.schema.json with the rfc3339 format checker wired. Satisfies Task 6 DoD item: one captured output per transport validates against the frozen v1 schema. All 19 contract tests pass; per-package coverage floors and all CI gates remain green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ctural, not byte) The embedded schema block is an abridged structural mirror — every validating keyword is identical to the canonical contracts/governance_read.v1.schema.json (legis CI asserts it), but description annotations live only in the canonical file. Prior "byte-equal" wording was inaccurate. Prose-only; the drift guard test is unchanged and still green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y at 1.3.0) src/legis/__init__.py carried a stale __version__ = "1.2.0" from the 1.3.0-prep that bumped pyproject.toml and CHANGELOG but not the constant, so `legis --version` reported 1.2.0 on the 1.3.0 line. Surfaced when deploying the local-main build for the warpline governance_read handshake. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ibling
Lands Plainweave in Legis's API: a read-only consumer of Plainweave's only
implemented producer, plainweave_preflight_facts_get / envelope
weft.plainweave.preflight_facts.v1 (ADR-006), which had no sibling consumer
and had never been exercised end-to-end.
Mirrors the existing warpline advisory-preflight read EXACTLY:
- legis/plainweave_preflight/client.py — injectable PlainweaveMcpClient +
StdioMcpInvoke; every contract fault fails CLOSED -> PlainweaveError. The
GV-LG-3 boundary is validated against Plainweave's real envelope shape
(data.authority_boundary.{local_only, live_peer_calls, governance_verdicts}
+ mandatory data.freshness/facts), since Plainweave's meta carries no
local_only/peer_side_effects (those are warpline's).
- service/preflight.read_plainweave_preflight — discriminated checked/unavailable
SIBLING of read_warpline_preflight; None/fault -> unavailable with a reason,
NEVER INTERNAL_ERROR, NEVER an empty-as-clean.
- mcp.py — plainweave_preflight_get tool next to warpline_preflight_get (separate
advisory sibling, not merged); runtime.plainweave wired from PLAINWEAVE_MCP_CMD,
default None -> unavailable; governance unaffected when absent/unconfigured.
ADVISORY ONLY, enrich-only: this read never changes a Legis policy/governance
decision. Tests pin it: a byte-identity test proves a hostile Plainweave client
cannot perturb a real verdict; a structural test proves no verdict-path function
references runtime.plainweave; a GV-LG-3 test refuses any producer claiming
governance_verdicts; the honest-degrade path (absent -> unavailable; ok:false
error envelope -> unavailable) is pinned.
Conformance oracle drives legis's real parser over a frozen golden built from the
producer contract (CONSTRUCTED, not live-captured — the hub session's MCP wiring
misroutes Plainweave; live end-to-end capture is a flagged follow-up in a
legis-rooted session). Gates green: ruff, mypy, pytest (1377 passed),
per-package coverage floors (plainweave_preflight 96.6%), sei oracle,
policy-boundary-check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion pending) + record Plainweave consumer; PDR-0007, PDR-0008 Checkpoint 2026-06-28. PDR-0007 (build governance_read.v1, cleared-only per-SEI read legis publishes for warpline, owner-directed) and PDR-0008 (record the parallel session's Plainweave advisory consumer under federation-read doctrine). North-star unchanged at 1 (federation work is seam-quality, not P2 findings). Escalation: the push/publish release decision for the now-substantial unpushed local main (G1 + Plainweave + 1.3.0 line) remains owner-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
….3.0 Records the two federation read surfaces added since the 1.3.0 section was written (PDR-0007 governance_read.v1 producer; PDR-0008 Plainweave advisory consumer) and updates the conformance note: the per-SEI governance read Warpline needs is now shipped on legis's producer side. Owner-directed: fold into 1.3.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The renderer (render_text) appends an audience tag ("[operator]",
"[auto-fixable]", "[fixed]") to every check line. Five operator-key
check messages in doctor.py already ended with a literal " [operator]",
causing the renderer to emit "[operator] [operator]" double-tags.
Remove the trailing " [operator]" from the four message strings in
check_posture_key_reset and check_operator_key_accessible; the renderer
continues to own the audience tag on every check line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 1.3.0 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Resolves candidate paths and fails closed (InvalidArgumentError) when any escapes the anchor; collapses .. and symlinks via resolve() - Deferred service.errors import to avoid a policy<->service cycle - Toward legis-0186c23a2c Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dary Closes legis-0186c23a2c. An MCP caller could pass an absolute or ..-bearing root/repo_root and make Legis walk/parse arbitrary Python trees visible to the server process. The tool now calls assert_within_boundary(source_root, ...) before any filesystem walk; an out-of-bounds root fails closed to an INVALID_ARGUMENT error envelope (never a scanned result / PASS). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The existing repo_root reject-test exercised containment only incidentally (its default root, repo_root/src, was also out of bounds), so dropping repo_root from assert_within_boundary would have left every MCP test green. Add a discriminating case (repo_root out-of-bounds, root explicitly in-bounds) + the symmetric scanned_root-absence assert; mutation-proven to fail if repo_root containment is removed. Final-review Important finding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plan for legis-0186c23a2c executed in this branch (subagent-driven TDD). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve the P1 layering-inversion items from the architecture handover — behavior-preserving moves that remove the most fragile package edges. - H-1 (B3): extract enforcement/signing.py verbatim into a new dependency-free leaf legis/crypto/signing.py (imports only legis.canonical). Repoint all source + test importers and delete the old module (no shim, so a future store->enforcement.signing import fails loudly). Kills the store<->enforcement bidirectional edge: store now imports crypto downward, as enforcement does. - H-2 (B5): move OperatorKeyCustodyError to a leaf legis/posture/errors.py; install re-exports it for back-compat; posture/ledger imports it from the leaf. Kills the posture->install edge (posture no longer imports the 1500-LOC setup module). - H-3 (B6): move _load_policy_cell_registry out of the MCP transport into policy/cells.load_policy_cell_registry; the HTTP app and MCP server both import it from there. Kills the api->mcp transport-on-transport edge. Fail-closed default preserved verbatim. - H-4 (B4): the suspected policy->service deferred import does not exist in the tree; record the leaf-direction rule (service->policy only) in policy/__init__. Add a crypto/ per-package coverage floor (93%) to preserve the protection signing.py had under enforcement's floor before the move. The cross-tool HMAC contract is untouched: the signing code is byte-identical and the byte-pinned vectors are unchanged. Full suite green; ruff/mypy clean; policy-boundary-check, SEI oracle, governance-gate, and per-package floors pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the policy_boundary_check scan-root containment security fix (assert_within_boundary primitive; legis-0186c23a2c).
Consolidates onto the 1.3.0 federation-reads line: the H-1..H-4 layering-inversion decoupling and the policy_boundary_check scan-root containment security fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-jp82-jpqv-5vv3) Fixes a high-severity request.form() DoS (form size/field limits silently ignored for application/x-www-form-urlencoded) and a low-severity request.url.hostname poisoning. fastapi 0.136.3 requires only starlette>=0.46.0, so the bump resolves cleanly with no other lock changes. Full suite + mypy green. The low-severity npm esbuild advisory (GHSA-g7r4-m6w7-qqqr) is in the docs-site build toolchain (site/), not the legis package; its parents cap it at ^0.27 so it cannot be bumped in isolation, and it only affects a Windows dev server. Left for a separate site-only change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 55efd065e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if not argv: | ||
| raise WarplineError("WARPLINE_MCP_CMD is blank") | ||
| from legis.config import project_root | ||
| warpline = WarplineMcpClient(invoke=StdioMcpInvoke(command=argv), repo=str(project_root())) |
There was a problem hiding this comment.
Use LEGIS_SOURCE_ROOT for Warpline repo
In MCP deployments that set LEGIS_SOURCE_ROOT to serve a repo other than the process cwd, the rest of build_runtime points git/source reads at that env value, but this Warpline client is constructed with Path.cwd(). The resulting warpline_preflight_get call sends repo=<cwd> for base..head, so the advisory impact/reverify facts can be computed against the wrong repository or degrade to unavailable even though the configured Legis source root is correct.
Useful? React with 👍 / 👎.
| proc = subprocess.run(self._command, input=stdin, capture_output=True, | ||
| timeout=self._timeout, shell=False, check=False, text=False) |
There was a problem hiding this comment.
Reject non-zero MCP subprocess exits
When the sibling MCP process emits a valid tools/call JSON-RPC response and then exits non-zero (for example a crash or shutdown error after writing the response), subprocess.run(..., check=False) leaves proc.returncode nonzero but this path still returns the structured payload as a successful checked advisory result. Since read_warpline_preflight only degrades on WarplineError, a transport failure can be reported as trusted advisory facts; check proc.returncode and raise before returning any parsed payload.
Useful? React with 👍 / 👎.
| if not isinstance(value, str): | ||
| return False | ||
| try: | ||
| datetime.fromisoformat(value.replace("Z", "+00:00")) |
There was a problem hiding this comment.
Reject naive governance_read timestamps
For signed clearances whose recorded_at is date-only or lacks a timezone (for example 2026-06-02 or 2026-06-02T12:00:00), datetime.fromisoformat() accepts the value, so governance_read.v1 emits it as as_of even though the contract requires an RFC3339 date-time and this function's comment says non-RFC3339 timestamps are omitted. This can produce schema-invalid checked records instead of safely dropping the malformed clearance.
Useful? React with 👍 / 👎.
| if meta.get("peer_side_effects"): | ||
| raise WarplineError(f"{tool} claims a peer side effect (GV-LG-3): {meta.get('peer_side_effects')!r}") |
There was a problem hiding this comment.
Require explicit peer_side_effects metadata
When Warpline returns meta.local_only: true but omits peer_side_effects (or sends another falsey malformed value), this check treats the envelope as side-effect-free and returns checked. The GV-LG-3 boundary depends on an explicit producer declaration that no peer side effects occurred, so malformed metadata should degrade to unavailable instead of being accepted as advisory facts.
Useful? React with 👍 / 👎.
…can edge The consolidation merged fix/policy-boundary-containment, whose boundary_scan.assert_within_boundary takes a deferred, call-time import of service.errors.InvalidArgumentError (fail-closed INVALID_ARGUMENT mapping). The H-4 docstring claimed policy->service was ABSENT, which is now false at the branch tip. Rewrite it to document that one deferred import as the sanctioned, cycle-safe exception (per the handover's stated fallback) and to forbid only EAGER policy->service imports. Honesty fix surfaced by the release review; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 659e11fb83
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| proc = subprocess.run(self._command, input=stdin, capture_output=True, | ||
| timeout=self._timeout, shell=False, check=False, text=False) |
There was a problem hiding this comment.
Reject non-zero Plainweave subprocess exits
When PLAINWEAVE_MCP_CMD writes a valid JSON-RPC response and then exits non-zero (for example a crash or shutdown error after emitting the response), check=False leaves proc.returncode nonzero but this path can still return structuredContent or parsed text as a successful checked preflight. Since read_plainweave_preflight only degrades on PlainweaveError, check proc.returncode and raise before returning any parsed payload.
Useful? React with 👍 / 👎.
| if not argv: | ||
| raise PlainweaveError("PLAINWEAVE_MCP_CMD is blank") | ||
| from legis.config import project_root | ||
| plainweave = PlainweaveMcpClient(invoke=PlainweaveStdioMcpInvoke(command=argv), repo=str(project_root())) |
There was a problem hiding this comment.
Launch Plainweave against the configured source root
In MCP deployments where LEGIS_SOURCE_ROOT points at a repo other than the server process cwd, the rest of build_runtime uses that configured source root, but this Plainweave setup uses project_root()/cwd and the client does not pass _repo in its tool arguments. The plainweave_preflight_get subprocess can therefore produce facts for the wrong checkout or report unavailable even though Legis is serving the configured repo; wire LEGIS_SOURCE_ROOT/runtime.source_root into the subprocess cwd or producer arguments.
Useful? React with 👍 / 👎.
| f"{tool} authority_boundary.governance_verdicts is not false: " | ||
| f"{boundary.get('governance_verdicts')!r}" | ||
| ) | ||
| if "freshness" not in data or "facts" not in data: # degraded -> unavailable, not bare empty 'checked' |
There was a problem hiding this comment.
Validate Plainweave fact fields before returning checked
When the Plainweave sibling returns an ok envelope with freshness and facts present but malformed, such as facts: null or an object instead of the contract array, this only checks key presence and returns checked. The tool description says payload shape mismatches should degrade to unavailable, and consumers will read preflight_facts.data.facts as the advisory fact list, so malformed fields should raise PlainweaveError rather than being passed through as trusted checked output.
Useful? React with 👍 / 👎.
Release 1.4.0 — consolidated release branch
Consolidates all pending work onto one release branch: the in-flight 1.3.0
federation-read line, the layering-inversion refactor, the
policy_boundary_checkcontainment security fix, and a
starlettesecurity bump. Supersedes #21(federation-reads), #22 (policy-boundary), and dependabot #11 (starlette).
43 commits · 98 files · +7462 / −585Included
Federation read surfaces (1.3.0 line)
governance_read.v1— the per-SEI governance read legis publishes forWarpline (CLI
legis governance-read, MCPgovernance_read, HTTPGET /governance/sei/{sei}/governance-read); frozen contract + cross-transportschema-agreement tests; cleared-only projection on an advisory boundary.
verdict path).
(
WarplineMcpClient/StdioMcpInvoke,WARPLINE_MCP_CMD); advisory boundarypreserved — governance verdicts stay byte-identical with or without Warpline.
Governance-honesty hardenings
read_floor()fails closed on a chain-integrity break (gates on thekeyless
verify_integrity()re-hash →structured, never a tampered floor).ProtectedGate.transaction()).policy_boundary_checkcontains scan roots to the source boundary(
assert_within_boundary; legis-0186c23a2c) — closes an absolute-path escape.Architecture — layering inversions decoupled (handover P1)
crypto/leaf (kills thestore ↔ enforcementedge; HMAC bytes + byte-pinned cross-tool vectorsunchanged).
OperatorKeyCustodyError→posture/errors.pyleaf (killsposture → install)._load_policy_cell_registry→policy.cells.load_policy_cell_registry(kills
api → mcp; fail-closed default preserved verbatim).policy → servicenon-edge confirmed absent + documented.Security
starlette1.2.1 → 1.3.1 (GHSA-82w8-qh3p-5jfq HIGHrequest.form()DoS +GHSA-jp82-jpqv-5vv3 LOW hostname poisoning). fastapi needs only
starlette>=0.46.0, so it resolves with no other lock changes.Release
pyproject,__init__,uv.lock,CHANGELOG).Deliberately NOT included
esbuildadvisory (GHSA-g7r4-m6w7-qqqr, LOW, Windows-dev-server-only) — atransitive dep in
site/(the docs site, not the legis wheel), capped by itsparents'
^0.27ranges so it can't be bumped in isolation. Left for a separatesite-only change.
Verification (on the branch tip)
Full suite 1389 passed / 9 skipped · coverage 92.5% · per-package floors hold
(incl. the new
crypto/floor) · ruff clean · mypy clean ·policy-boundary-checkPASS · SEI oracle 8/8 ·
governance-gatePASS · cross-tool signing contract intact(byte-pinned vectors untouched).
🤖 Generated with Claude Code