Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
# copied verbatim into the build output). It consumes the shared
# @weft/site-kit, which lives in a SUBDIRECTORY of a DIFFERENT repo (the weft
# hub). npm cannot install a git subdirectory directly, so a fetch step
# sparse-checks-out packages/site-kit into site/vendor/site-kit/ before
# `npm install` resolves the `file:./vendor/site-kit` dependency.
# sparse-checks-out packages/site-kit at a pinned reviewed commit into
# site/vendor/site-kit/ before `npm install` resolves the `file:./vendor/site-kit`
# dependency.
name: Deploy legis site

on:
Expand Down Expand Up @@ -51,7 +52,7 @@ jobs:
node-version: 22

- name: Fetch @weft/site-kit
# Sparse-fetch packages/site-kit from the weft hub repo into
# Sparse-fetch packages/site-kit from the pinned Weft commit into
# vendor/site-kit/ so the file: dependency resolves on install.
run: npm run fetch-site-kit

Expand Down
56 changes: 38 additions & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,26 @@ jobs:
- name: Install dependencies
run: uv sync --dev

- name: Verify lockfile
run: uv lock --check

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 Check the lock before mutating sync

In this workflow, the lock check runs after uv sync --dev. I checked uv sync --help: --locked is the option that asserts uv.lock remains unchanged, while uv lock --check only checks the current lockfile state. If pyproject.toml and uv.lock drift on a release, the preceding sync can refresh uv.lock in the runner and this check then passes, letting publish proceed from an unreviewed lock; run the check before syncing or use uv sync --locked --dev here.

Useful? React with 👍 / 👎.


- name: Run lint
run: uv run ruff check src

- name: Test gate (never publish a red build)
run: uv run pytest -q
run: uv run pytest --cov=legis --cov-report=term-missing --cov-report=json --cov-fail-under=88

- name: Enforce per-package coverage floors
run: uv run python scripts/check_coverage_floors.py

- name: Run SEI conformance oracle
run: uv run pytest tests/conformance/test_sei_oracle.py

- name: Run type check
run: uv run mypy src/legis

- name: Run policy-boundary honesty gate
run: uv run legis policy-boundary-check --root src --repo-root .

- name: Verify release tag matches project version
env:
Expand Down Expand Up @@ -61,44 +79,46 @@ jobs:
env:
LOOMWEAVE_URL: ${{ vars.LOOMWEAVE_URL }}
LOOMWEAVE_LIVE_ORACLE_LOCATOR: ${{ vars.LOOMWEAVE_LIVE_ORACLE_LOCATOR }}
LEGIS_LOOMWEAVE_HMAC_KEY: ${{ secrets.LEGIS_LOOMWEAVE_HMAC_KEY }}
steps:
- uses: actions/checkout@v4

# Skip-not-fail: when the release environment is not provisioned with the
# live oracle config, this job passes as a fast no-op so it never blocks
# the PyPI publish (the rc4 blocker f95036b removed — do not reintroduce
# it). When the config IS present, the oracle runs for real and a
# conformance failure blocks publish — the gate still bites where it can.
- name: Detect live oracle configuration
id: oracle_config
- name: Require live oracle configuration
run: |
missing=()
for name in LOOMWEAVE_URL LOOMWEAVE_LIVE_ORACLE_LOCATOR LEGIS_LOOMWEAVE_HMAC_KEY; do
for name in LOOMWEAVE_URL LOOMWEAVE_LIVE_ORACLE_LOCATOR; do
if [ -z "${!name}" ]; then
missing+=("${name}")
fi
done
if [ "${#missing[@]}" -ne 0 ]; then
joined="$(IFS=', '; echo "${missing[*]}")"
echo "::notice::Live Loomweave oracle not provisioned (${joined} unset) — skipping conformance, not blocking publish."
echo "configured=false" >> "$GITHUB_OUTPUT"
else
echo "configured=true" >> "$GITHUB_OUTPUT"
echo "::error::Missing required release conformance environment: ${joined}"
exit 1
fi

- uses: astral-sh/setup-uv@v5
if: steps.oracle_config.outputs.configured == 'true'
with:
enable-cache: true

- name: Install dependencies
if: steps.oracle_config.outputs.configured == 'true'
run: uv sync --dev

- name: Run live Loomweave oracle
if: steps.oracle_config.outputs.configured == 'true'
run: uv run pytest tests/conformance/test_live_loomweave_oracle.py
env:
LEGIS_LOOMWEAVE_HMAC_KEY: ${{ secrets.LEGIS_LOOMWEAVE_HMAC_KEY }}
run: |
missing=()
for name in LOOMWEAVE_URL LOOMWEAVE_LIVE_ORACLE_LOCATOR LEGIS_LOOMWEAVE_HMAC_KEY; do
if [ -z "${!name}" ]; then
missing+=("${name}")
fi
done
if [ "${#missing[@]}" -ne 0 ]; then
joined="$(IFS=', '; echo "${missing[*]}")"
echo "::error::Missing required release conformance environment: ${joined}"
exit 1
fi
uv run pytest tests/conformance/test_live_loomweave_oracle.py

publish:
name: Publish to PyPI
Expand Down
34 changes: 33 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@ versions per [PEP 440](https://peps.python.org/pep-0440/) /

_Post-1.0.0 work lands here; legis versions independently from the Weft 1.0 launch on._

## [1.1.1] — 2026-06-23

Security and release-readiness hardening after the 1.1.0 dogfood release.

### Fixed

- **Posture floor reads now fail closed and ignore metadata tails.** Missing or
uninitialized posture ledgers resolve to the structured floor instead of
crashing or inheriting a self-clearable registry default, and session metadata
appended after a floor transition can no longer become the effective floor.
- **Operator elevation sessions are authenticated.** Session files now carry a
backend-signed HMAC; forged or stale session metadata is refused before a
posture transition can be written.
- **Rekey recovery preserves the standing floor and is doctor-verifiable through
age-file custody.** Rekey appends a loud `KEY_RESET` without lowering posture,
and doctor can verify the follow-up acknowledgment through the default
age-file backend when the operator provides the passphrase.
- **Explicit posture installation fails closed when key custody is not
configured.** Bare `legis install` may still defer posture setup, but
`legis install --posture` now exits non-zero if no GENESIS can be written.
- **Governance and override authority checks are stricter.** Supplied SEIs must
bind to the asserted entity before a governance record is accepted, and
coached HTTP overrides require an enabled judge instead of recording a
no-judge success path.
- **Install, release, and site-kit custody are tighter.** `.mcp.json`
registration rejects non-canonical Legis command heads while preserving safe
operator env, release publication requires live Loomweave oracle conformance,
release secrets are scoped to the oracle step, and the fetched Weft site-kit
dependency is pinned.

## [1.1.0] — 2026-06-19

Three defects surfaced by a `lacuna` dogfooding pass, confirmed (investigation +
Expand Down Expand Up @@ -618,7 +648,9 @@ WP-M1 service-layer extraction, consolidated behind a stable version.
`HTTPException`, so both HTTP and the forthcoming MCP adapter drive one code
path. Behavior-preserving; FastAPI handlers are now thin adapters.

[Unreleased]: https://github.com/foundryside-dev/legis/compare/v1.0.0...HEAD
[Unreleased]: https://github.com/foundryside-dev/legis/compare/v1.1.1...HEAD
[1.1.1]: https://github.com/foundryside-dev/legis/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/foundryside-dev/legis/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/foundryside-dev/legis/compare/v1.0.0rc4...v1.0.0
[1.0.0rc4]: https://github.com/foundryside-dev/legis/compare/v1.0.0rc3...v1.0.0rc4
[1.0.0rc3]: https://github.com/foundryside-dev/legis/compare/v1.0.0rc2...v1.0.0rc3
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ Legis is the fourth Weft product: the git/CI and governance side of the suite's

## Status

Legis is at **`1.0.0`** — the gold release. The standalone git/CI surfaces, the graded 2x2 enforcement engine, the agent-programmable policy grammar, SEI-keyed attestations, and the Wardline/Filigree suite combinations are built and tested. The git-rename provider to Loomweave is contract-locked, operative pending Loomweave's committed-range driving.
Legis is at **`1.1.1`** — the gold release line with post-launch posture, install, and release-gate hardening. The standalone git/CI surfaces, the graded 2x2 enforcement engine, the agent-programmable policy grammar, SEI-keyed attestations, and the Wardline/Filigree suite combinations are built and tested. The git-rename provider to Loomweave is contract-locked, operative pending Loomweave's committed-range driving.

The transport-agnostic service layer (WP-M1) and the agent-facing MCP surface on top of it have landed (`legis mcp`). The MCP surface now declares output schemas across its tools, exposes read-side governance/diagnostic tools (`doctor_get`, `override_list`, `policy_boundary_check`, lineage-honesty reads, `check_report`, `signoff_bind_issue`), and keeps the API/MCP/CLI paths routed through the same service layer instead of duplicating governance decisions.

Legis stands itself up with `legis install`: instruction block, `legis-workflow` skill pack, SessionStart hook, `.mcp.json` registration, and the Legis-only `.weft/legis/` ignore rule. `legis doctor [--fix]` provides an operator health view and safe repair for the install + config layer, tagging each problem `[auto-fixable]` or `[operator]` so it is clear what `--fix` will and will not touch. Doctor names enablement paths when governance is unwired (policy cells, Wardline routing), but it reports rather than auto-enabling policy surfaces or touching signing keys.

Gold was earned, not declared: 1.0.0 was first cut on 2026-06-09, then re-opened when a P0 governance-honesty false-green (G1 — an absent Wardline `findings` key routing zero defects under a green status) was caught *after* the cut. The fix, the cross-member conformance vector that makes it real, and a small batch of follow-through hardening shipped before final. See the combination matrix below for per-pairing status and `CHANGELOG.md` for the full release notes.
Gold was earned, not declared: 1.0.0 was first cut on 2026-06-09, then re-opened when a P0 governance-honesty false-green (G1 — an absent Wardline `findings` key routing zero defects under a green status) was caught *after* the cut. The 1.1.x line keeps that surface hardened: posture floors fail closed, operator sessions are signed, rekey recovery preserves the standing floor, and release publication is gated on live Loomweave conformance. See the combination matrix below for per-pairing status and `CHANGELOG.md` for the full release notes.

### Last week in practical terms

Expand Down Expand Up @@ -135,7 +135,7 @@ Legis is a governance-*honesty* tool, so it states its own residual limits plain

- **The coached cell is a model-robustness wall, not a cryptographic one.** A blocked agent clears the coached gate by convincing the LLM judge; a *malicious prompt injection* that persuades the model will likewise clear it. Structural injection (forging a verdict key) is closed and any transport/parse failure is fail-closed to `BLOCKED`, but the coached cell has no defense-in-depth against a model that is genuinely fooled. For verdicts that must not rest on the model's word, use the **protected** cell, where a judge `ACCEPTED` is advisory only and is downgraded to require operator sign-off (unless a deterministic, non-LLM validator confirms it).
- **Tamper-evidence assumes the signing key is out of the attacker's reach, and is not absolute against raw DB-file writes.** v3 signing binds each record's chain position, so in-place edits, reordering, and renumbering are detected. A holder of raw write access to the governance `.db` can still *delete* a record and re-chain, or rewrite a record's policy to a non-protected value and strip its protected markers ("modify-to-unsigned"), or truncate the tail — these are residuals of the conceded raw-file-write threat tier. The opt-in `HeadAnchor` mitigates truncation/rewind (with a documented anchor-replay caveat). `legis doctor` now refuses to bless zero-byte or missing-schema audit stores without creating replacement tables, but that is an operator diagnostic, not a substitute for storage custody. Keep the governance store on storage only the operator controls.
- **The operator session file is a metadata record, not an encrypted vault.** A process with read access to `.weft/legis/operator_session.json` can read the keychain item id and, if it also has keychain access, produce arbitrary signatures during the window. This is the same tier as raw-DB-write access. The mitigation is OS keychain access control (the item accessible only to the legis process user), not file encryption of the session file. The file never holds the key, a passphrase, or a raw age blob — only window metadata and a backend-specific unlock reference (`None` for the age-file/env backends, where re-prompt is the unlock).
- **The operator session file is authenticated metadata, not an encrypted vault.** A process with read access to `.weft/legis/operator_session.json` can read the keychain item id and the session metadata signature; if it also has keychain access, it can produce arbitrary signatures during the window. This is the same tier as raw-DB-write access. The mitigation is OS keychain access control (the item accessible only to the legis process user), not file encryption of the session file. The file never holds the key, a passphrase, or a raw age blob — only window metadata, a backend-specific unlock reference (`None` for the age-file/env backends, where re-prompt is the unlock), and an HMAC proving the operator key opened that session.
- **Durability tier.** The audit store runs `synchronous=FULL`, but a power loss can still drop the most recent un-checkpointed appends; the trail stays internally consistent (a shortened-but-valid tail), it does not corrupt.
- **SEI binding integrity rests on TLS by design.** The Weft request HMAC authenticates legis's *requests* to Loomweave; it does not sign Loomweave's *responses*. Filigree binds are transport-open and rely on TLS plus the app-level `binding_signature` and local `BindingLedger` evidence, not on `X-Weft-*` headers. `LEGIS_ALLOW_INSECURE_REMOTE_HTTP=1` still permits plaintext to a remote sibling and therefore **voids that custody seal** (an on-path attacker could forge a stable identity binding) — it logs a warning and is for dev/loopback use only.

Expand Down
Loading
Loading