Skip to content

Release 1.4.0 — federation reads + layering decouple + policy-boundary & starlette security fixes#24

Merged
tachyon-beep merged 44 commits into
mainfrom
release/1.4.0
Jun 29, 2026
Merged

Release 1.4.0 — federation reads + layering decouple + policy-boundary & starlette security fixes#24
tachyon-beep merged 44 commits into
mainfrom
release/1.4.0

Conversation

@tachyon-beep

Copy link
Copy Markdown
Collaborator

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_check
containment security fix, and a starlette security bump. Supersedes #21
(federation-reads), #22 (policy-boundary), and dependabot #11 (starlette).

43 commits · 98 files · +7462 / −585

Included

Federation read surfaces (1.3.0 line)

  • governance_read.v1 — the per-SEI governance read legis publishes for
    Warpline (CLI legis governance-read, MCP governance_read, HTTP
    GET /governance/sei/{sei}/governance-read); frozen contract + cross-transport
    schema-agreement tests; cleared-only projection on an advisory boundary.
  • Plainweave advisory-preflight consumer (advisory sibling; never read by a
    verdict path).
  • Warpline advisory preflight rewired onto Warpline's real MCP wire
    (WarplineMcpClient / StdioMcpInvoke, WARPLINE_MCP_CMD); advisory boundary
    preserved — governance verdicts stay byte-identical with or without Warpline.

Governance-honesty hardenings

  • Posture read_floor() fails closed on a chain-integrity break (gates on the
    keyless verify_integrity() re-hash → structured, never a tampered floor).
  • Protected-cell batches advance the HeadAnchor (ProtectedGate.transaction()).
  • policy_boundary_check contains scan roots to the source boundary
    (assert_within_boundary; legis-0186c23a2c) — closes an absolute-path escape.

Architecture — layering inversions decoupled (handover P1)

  • H-1: signing primitive → dependency-free crypto/ leaf (kills the
    store ↔ enforcement edge; HMAC bytes + byte-pinned cross-tool vectors
    unchanged).
  • H-2: OperatorKeyCustodyErrorposture/errors.py leaf (kills
    posture → install).
  • H-3: _load_policy_cell_registrypolicy.cells.load_policy_cell_registry
    (kills api → mcp; fail-closed default preserved verbatim).
  • H-4: policy → service non-edge confirmed absent + documented.

Security

  • starlette 1.2.1 → 1.3.1 (GHSA-82w8-qh3p-5jfq HIGH request.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

  • Version bumped 1.3.0 → 1.4.0 (pyproject, __init__, uv.lock, CHANGELOG).

Deliberately NOT included

  • npm esbuild advisory (GHSA-g7r4-m6w7-qqqr, LOW, Windows-dev-server-only) — a
    transitive dep in site/ (the docs site, not the legis wheel), capped by its
    parents' ^0.27 ranges so it can't be bumped in isolation. Left for a separate
    site-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-check
PASS · SEI oracle 8/8 · governance-gate PASS · cross-tool signing contract intact
(byte-pinned vectors untouched).

🤖 Generated with Claude Code

tachyon-beep and others added 30 commits June 26, 2026 15:40
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>
tachyon-beep and others added 13 commits June 28, 2026 00:15
…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread src/legis/mcp.py
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()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +109 to +110
proc = subprocess.run(self._command, input=stdin, capture_output=True,
timeout=self._timeout, shell=False, check=False, text=False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment on lines +63 to +64
if meta.get("peer_side_effects"):
raise WarplineError(f"{tool} claims a peer side effect (GV-LG-3): {meta.get('peer_side_effects')!r}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +137 to +138
proc = subprocess.run(self._command, input=stdin, capture_output=True,
timeout=self._timeout, shell=False, check=False, text=False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread src/legis/mcp.py
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()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@tachyon-beep tachyon-beep merged commit 3055d2c into main Jun 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant