Skip to content

1.0.0rc4: federation WEFT_FEDERATION_TOKEN + dogfood 2026-06-06 (gate clarity, legis dev-loop, payload controls, 401 distinction)#34

Merged
tachyon-beep merged 32 commits into
mainfrom
rc4
Jun 7, 2026
Merged

1.0.0rc4: federation WEFT_FEDERATION_TOKEN + dogfood 2026-06-06 (gate clarity, legis dev-loop, payload controls, 401 distinction)#34
tachyon-beep merged 32 commits into
mainfrom
rc4

Conversation

@tachyon-beep

Copy link
Copy Markdown
Collaborator

1.0.0rc4 — adopt federation-scoped WEFT_FEDERATION_TOKEN

The federation loopback token was renamed WEFT_FEDERATION_TOKEN (deconfliction
plumbing across the Weft federation). Wardline adopts it for lockstep: the original
"use Wardline's own name to avoid coupling to a sibling FILIGREE_* name" rationale
dissolves now that the name is federation-scoped rather than a sibling.

  • filigree/config.py reads WEFT_FEDERATION_TOKEN first (env → .env), then the
    deprecated fallback WARDLINE_FILIGREE_TOKEN (env → .env). New-name-fully-then-old-name-fully precedence.
  • The 401 auth-rejected hints (CLI + MCP) now name WEFT_FEDERATION_TOKEN — this
    supersedes the WARDLINE_FILIGREE_TOKEN text in concern chore(deps-dev): update pymdown-extensions requirement from >=10.7 to >=10.21.2 #5 below.
  • Silent fallback (no runtime deprecation warning — "deconfliction plumbing, not
    security"); documented under CHANGELOG [Unreleased]. rc2 history lines left verbatim.
  • Existing deployments (e.g. lacuna's gitignored .env) keep working unchanged via the
    fallback and can migrate at leisure.

Suite 2525 passing, ruff + format + mypy clean. New tests cover precedence,
both-names, and the legacy fallback (config + end-to-end install path).


Closes the actionable wardline items from the 2026-06-06 Loom dogfood friction report
(label `dogfood-2026-06-06`). All five concerns from the re-test are addressed and
live-verified against a freshly-spawned MCP server.

⚠️ Deployment note for the federation (read first)

The re-test reported #2/#3/#4 as "not addressed". Root cause: stale long-running
`wardline mcp` processes
, not missing code. The install is editable
(`~/wardline/src`), so a fresh spawn already has every fix — but seven long-lived MCP
servers were frozen at their spawn-time source (one was internally inconsistent and
crashed on `GateDecision.reason`). The re-tester tested #1 via the CLI (fresh process →
worked) and #2/#3/#4 via a stale MCP server (→ looked unaddressed).

Action required after merge: partners must restart their `wardline mcp` server
(or session)
to pick up the code. No restart ⇒ same "broken" output.

Fixes

# Concern Fix Issue
1 (P0) `--allow-dirty` on `scan --format legis` unsigned, `dirty:true`-marked dev artifact; signing stays clean-tree-only wardline-30f3d38fa5
2 (P1) gate contradicts its summary `gate.reason` + `gate.evaluated`; `next_actions` now gate-aware (no "rescan after edits" on a tripped gate) wardline-be75c6676d
3 (P1) silent gate-default breaking change `gate.migration_hint` (CLI stderr + MCP) + `UPGRADING.md` wardline-5f662e7a4f
4 (P1) `where` didn't shrink payload; `explain` blew budget `where` filters the agent_summary; `summary_only`/`max_findings`/`include_suppressed`; default explain cap (10); `truncation` block wardline-2957009961
5 (P2) 401 reported as "could not reach" `EmitResult.status`/`auth_rejected`; CLI/MCP print "401 (auth rejected) … set WARDLINE_FILIGREE_TOKEN"; stays soft wardline-53a44a3bb1

Live verification (fresh server, the re-tester's exact scenarios)

  • `tools/list` exposes the new `summary_only`/`max_findings`/`include_suppressed` args (proves fresh server).
  • 34-baselined gate trip → `gate.reason`, `gate.evaluated`, `gate.migration_hint`, and gate-aware `next_actions` all present; no crash.
  • `where:{active,CRITICAL}` (0 match) + `explain:true` → 1,585 chars (was 57,639), `suppressed_findings` empty inline, `truncation` present.
  • `summary_only` → 0 finding bodies, counts intact.

Tests / quality

Full suite 2482 passing, ruff + mypy + mkdocs-strict clean. Every fix is TDD'd
(red→green); golden legis signature byte-unchanged; CLI↔MCP parity preserved; CLI
`--format agent-summary` output unchanged.

🤖 Generated with Claude Code


Supersedes #30 (head branch renamed fix/dogfood-2026-06-06-gate-legis-payloadrc/1.0.0rc2; GitHub cannot move a PR's head branch, so this is its continuation). Adds the PR #30 multi-reviewer hardening pass and cuts release candidate 1.0.0rc2.


Continued from #32 (auto-closed by the rc2→rc3 branch rename). Version bumped to 1.0.0rc3.


Continues #33 (head branch renamed rc3rc4; GitHub closes a PR when its head branch is renamed, so this is its continuation). Adds the federation WEFT_FEDERATION_TOKEN adoption and cuts release candidate 1.0.0rc4.

John Morrissey and others added 28 commits June 6, 2026 12:44
…efusing (wardline-30f3d38fa5)

Dogfood friction #1: on a dirty tree `scan --format legis` failed exit 2 naming
an `allow_dirty` flag that was never exposed on the CLI — presenting identically
to "legis is broken." Expose `--allow-dirty` (CLI) / `allow_dirty` (MCP scan).

The honest fix: a dirty tree under allow_dirty does NOT sign. The only tree_sha
readable is the *committed* one, which does not describe dirty working content —
signing it would be false provenance (the `_git_tree_sha` guard). Instead it
falls through to the UNSIGNED dev artifact, clearly marked `dirty: true` (legis
records it `unverified`). Signing stays clean-tree-only; verification stays
clean-tree/CI. The loud refusal without --allow-dirty is unchanged.

CLI emits a stderr warning when the artifact is dirty/unsigned; MCP reports
`signed:false` + `dirty:true` in legis_artifact_status. legis ignores the unknown
`dirty` top-level key on the unverified path, so ingest is unaffected; the golden
clean-tree signature is byte-unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rdline-be75c6676d)

Dogfood friction #2: a scan reporting summary.active:0 AND gate.tripped:true read
as a bug — the agent had to run scan twice (with/without trust_suppressions) and
read --help to learn the gate evaluates the unsuppressed (baselined-included)
population by default.

GateDecision now carries `reason` and `evaluated`. `reason` names the count and
class that decided the verdict — "1 suppressed ERROR+ defect(s) (baseline/waiver/
judged) not cleared; pass --trust-suppressions (trusted checkout) or --new-since
<ref> (PR)" when the trip is solely from suppressed-but-gated findings, "N active
ERROR+ defect(s)" on a genuine trip (no misdirection to the suppression flags),
and the mixed form when both. `evaluated` names the population: "unsuppressed
(repository baseline/waiver/judged ignored)" by default, "post-suppression …
honored" under --trust-suppressions. Counts come from `gate_breakdown` over the
ANNOTATED findings so they match what the agent reads in `summary`.

Surfaced in the MCP scan gate block, the agent_summary gate block, and on CLI
stderr when the gate trips (never a silent exit 1). Both None when no --fail-on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dline-5f662e7a4f)

Dogfood friction #3: the secure gate-default (gate on the unsuppressed population)
is correct, but the rollout was silent — a repo whose committed baseline used to
clear --fail-on goes red with no code change, and an agent can't tell whether IT
broke scan or HEAD was already red.

New `baseline_migration_hint`: fires ONLY in the exact 'my repo went red with no
code change' case — a committed .wardline/baseline.yaml exists, the gate trips
SOLELY because baselined defects re-enter the unsuppressed population (no
genuinely-active defect, no waiver/judged-only trip), and neither
--trust-suppressions nor --new-since was passed. It points at both escape hatches
and UPGRADING.md. Silent on a genuine active trip, a trusted/PR-scoped run, or no
baseline file.

Surfaced loudly on CLI stderr and as MCP `scan` gate.migration_hint (None
otherwise). New UPGRADING.md documents the secure-default migration; CHANGELOG
[Unreleased] gains entries for dogfood #1/#2/#3. Secure default unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y/max_findings/include_suppressed, default explain cap (wardline-2957009961)

Dogfood friction #5: the documented cost lever (`where`) did not control cost and
one-shot `explain:true` was unusable on a real repo.

- `where` now filters the agent_summary arrays too (it only filtered the top-level
  findings list before) — a filter matching 0 findings no longer returns dozens of
  suppressed findings inline. agent_summary build takes a display_findings view;
  its summary COUNTS stay whole-project.
- New `summary_only:true` (counts + gate, no bodies — smallest "did the gate pass?"
  payload), `include_suppressed:false` (drop suppressed bodies; counts stay),
  `max_findings:N` (cap returned bodies).
- DEFAULT explain ceiling: `explain:true` inlined provenance for EVERY active
  defect (56,820 chars on one line over a whole repo). Capped at 25 by default;
  max_findings tightens it. Findings past the cap are still returned, sans inline
  explanation.
- New `truncation` block (findings_total/findings_returned/findings_truncated/
  explanations_truncated/summary_only/include_suppressed/max_findings) so a bounded
  payload is never mistaken for "covered everything."

CLI --format agent-summary is byte-unchanged (defaults preserve whole-project,
uncapped behaviour). Docs (agents.md, legis-handoff.md --allow-dirty) + CHANGELOG
updated. Full suite 2476 green; ruff/mypy/mkdocs-strict clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…wardline-be75c6676d follow-up)

The gate reason counted `gate_breakdown(result.findings)` — the annotated population —
so under `--new-since` a delta-scoped-out defect (converted to BASELINED by
apply_delta_scope) was wrongly counted as "suppressed >= threshold", inflating the
count and pointing at `--new-since` (already supplied).

_gate_reason now classifies the defects that ACTUALLY gate (the unsuppressed gate
population, where out-of-delta defects are BASELINED and so excluded) by their state in
the emitted findings. The count is exactly what tripped the gate; the `--new-since`
path no longer over-counts. The trust-suppressions branch is unchanged (gate == emitted
findings there). Locked by extending the new_since differential to assert 1, not 2.

Verified: legis `ScanResultsIn.scan` is typed `dict` (arbitrary mapping), so the new
unsigned `dirty:true` marker rides through intake untouched — confirmed the dev artifact
stays postable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ow-up)

The reported one-shot blowup was 56,820 chars over 34 findings and exceeded the tool
token limit; a default of 25 inlined provenances was still uncomfortably close. Lower
the default ceiling to 10 — comfortably under the limit, still plenty to triage in one
call — and let max_findings RAISE it when the agent explicitly accepts the larger
payload (summary_only covers the common "did the gate pass?" case). New test locks that
max_findings can lift the count above the default. Docs/CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ped gate (wardline-be75c6676d follow-up)

Dogfood re-test, #2 "Worse" half: when the gate trips solely on baselined findings
summary.active is 0, so next_actions said "no active defects; rescan after edits" —
telling the agent it PASSED while the gate FAILED.

_next_actions_for now takes the GateDecision. With 0 active defects but a tripped
gate it emits a scan action whose reason names the gate failure + the escape hatches
(trust_suppressions / new_since / clear the baseline; see gate.reason /
gate.migration_hint) instead of the passive "rescan after edits". The active>0 and
genuinely-clean paths are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le (wardline-53a44a3bb1)

Dogfood #5: a 401 (token absent from the CLI env) was reported as "could not reach
Filigree" — a wrong diagnosis that sent the agent chasing a broken-bridge / wrong-
endpoint theory. The prior seam work deliberately made 401/403 SOFT (auth failure must
not crash the scan loop); that is kept — only the MESSAGE changes.

EmitResult now carries `status` (the HTTP status when one reached us; None when the
transport itself failed) and `auth_rejected` (the 401/403 case). The CLI prints
"Filigree returned 401 (auth rejected) … set WARDLINE_FILIGREE_TOKEN" vs a 5xx
"server error" vs the genuine "could not reach"; the MCP scan filigree_emit block and
agent_summary carry the same discriminated disabled_reason. 401/403 stays
reachable=False (non-load-bearing), never exit-2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nreachable (#5)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ince the rebrand)

uv.lock still carried the pre-rebrand `clarion` optional-dependency extra; pyproject
already renamed it to `loomweave` (Clarion→Loomweave). Regenerated to match — no
dependency change (blake3 >=1.0, unchanged), just the extra name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bda branch-locality, finding-lifecycle glossary

Resolves three Filigree ready-queue items, built TDD with adversarial review.

PY-WL-110 weft_markers soundness gap (wardline-d62845bb18, P2)
  contradictory_trust.py hardcoded `wardline.decorators.*` as the only marker
  prefix, silently missing contradictory stacks imported from the renamed
  `weft_markers` shim. Now derives _MARKER_NAMES + _MARKER_MODULE_PREFIXES from
  BUILTIN_BOUNDARY_TYPES so the rule can't drift from the grammar. +2 tests.

Lambda bindings are branch-local (wardline-36016d26f3, P3)
  _CURRENT_LAMBDA_BINDINGS was shared across if/else, try/except, match arms,
  leaking a lambda bound in one arm into siblings (over-fire). Each arm now walks
  an arm-local copy.

  NOTE: the first cut of the merge-out (clear()+full-union with the synthetic
  fall-through arm last) introduced a *false-negative regression* — verified
  empirically against HEAD: a lambda rebound in a no-else `if` / no-catch-all
  `match` and called after the branch resolved EXTERNAL_RAW on HEAD but INTEGRAL
  after the naive fix. Replaced with a delta merge (layer each arm's net
  add/changed bindings onto the pre-branch state in source order) that keeps the
  leak fix AND reproduces HEAD's after-branch bindings, so no new false negative.
  +3 over-fire guards, +3 no-false-negative guards.

Finding-lifecycle vocabulary glossary (wardline-26e84dbd44, P3)
  Audited wardline's own usage: `active` is already the canonical word on every
  surface except the CLI summary, which printed `N new`. Relabelled to `N active`
  (text only; no JSON/SARIF/wire field renamed). Added the canonical glossary
  docs/reference/finding-lifecycle-vocabulary.md (single source of truth for
  new/active/suppressed/baselined/waived/judged + emitted-active vs gate
  population) with discipline tests + nav wiring. Cross-tool asks (Filigree
  first-seen "new", legis active) recorded as coordination context, not renamed.

Full suite 2471 passed, ruff + mypy clean, mkdocs --strict OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, MCP legis reason, strict arg validation

Applies the PR #30 multi-reviewer findings (code/tests/errors/comments/types):

- GateDecision.__post_init__ makes "tripped gate that reads as passed" (dogfood
  #2) unconstructible, not merely avoided by the factory.
- Filigree 403 is now distinguished from 401 across all three render sites
  (CLI stderr, CLI disabled_reason, MCP) — "forbidden (token lacks access)"
  rather than the misleading "set WARDLINE_FILIGREE_TOKEN".
- MCP dirty-unsigned legis artifact carries a loud `reason` (parity with the
  CLI "never gate CI on it" warning) — agent-first surfaces stay equally loud.
- migration_hint threaded into the agent-summary gate block so the
  "see gate.migration_hint" pointer in next_actions resolves on that surface too.
- Strict boolean validation for summary_only/include_suppressed/allow_dirty/
  explain (reject non-bool rather than silently coercing "false"→True) +
  max_findings JSON schema gains `minimum: 0`.
- CHANGELOG: payload-controls entry corrected to dogfood #4 (verified against
  the friction report: #4=payload, #5=auth); genuine-trip reason quoted verbatim.
- Glossary file:line anchors tightened to the WAIVED/JUDGED assignment lines.

Quality consolidation (behavior-preserving): shared severity_gates() and
filigree_disabled_reason() helpers, enum-identity (`is`) unified.

New tests pin 5xx rendering (CLI+MCP), the MCP legis dirty/signed projection,
the mixed active+suppressed gate-reason branch, the GateDecision invariant
guard, strict arg validation, and the agent-summary migration_hint.

Suite 2515 passed; ruff/mypy clean; mkdocs --strict builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Increment the release candidate (rc1 → rc2) to carry the PR #30 review
hardening (gate invariant, 403/5xx distinction, strict MCP arg validation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ust version test

- CHANGELOG: stamp the accumulated [Unreleased] work as [1.0.0rc2] - 2026-06-06
  and open a fresh empty [Unreleased]; consolidate the two `### Added` blocks
  into one (no content change, removes a Keep-a-Changelog duplicate-section smell).
- README: the quick-start scan output said "1 new" — corrected to "1 active",
  matching the CLI relabel shipped in this same release (and getting-started.md).
- test_package: assert __version__ starts with "1.0.0" (release line) instead of
  the exact rc suffix, so cutting a new rc no longer breaks the test.

Suite 2515 passed; ruff clean; mkdocs --strict builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`ruff format --check src tests` (run in CI's Lint+Format job) was red. Reformats
6 test files: two touched in this rc2 work (test_run.py, test_server_query_explain.py),
test_variable_level.py (dogfood branch change), and three with pre-existing drift
already on main (test_legis_intake_contract.py, test_client.py, test_sei_client_wire.py)
— the gate checks the whole tree, so all six must be clean. Formatting only; no
behavior change. Suite 2515 passed; ruff check + format + mypy all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, FP guard, doc-anchor rot)

Addresses the three Important findings from the PR #32 review panel, each
validated with an actual RED->GREEN cycle under debugging discipline.

I-1 EmitResult contradictory states (core/filigree_emit.py):
  - auth_rejected is now a derived @Property (status in {401,403}), deleting the
    redundant axis so "auth-rejected (200)" is unrepresentable, not merely unbuilt.
  - __post_init__ guard mirrors GateDecision: a reachable/success result carries no
    error status; a soft-failure created/updated nothing. Rejects reachable+503.
  - Docstring corrected (status is the error status; None on transport-fail AND 2xx).
  - No wire change: server.py still serializes auth_rejected via the property.

I-2 false-positive guard for PY-WL-110 (test_contradictory_trust.py):
  - Empirically: a foreign-only marker stack is filtered at the anchoring gate
    (provenance "fallback"), never reaching the line-81 prefix check. Added both the
    system-level test and the isolating test (real trust_boundary anchor + a
    coincidental foreign `trusted`). Mutation-proven: breaking the prefix check makes
    the isolating test fire a false PY-WL-110.

I-3 stale file:line anchors (finding-lifecycle-vocabulary.md):
  - Re-derived every churned-file anchor from HEAD; corrected ~26 citations.
  - Added a two-way content-binding discipline test: each load-bearing anchor's token
    must be on the cited source line AND the doc must cite that line, so doc and code
    can never silently diverge again.

Full suite 2520 passed; ruff/format/mypy clean; mkdocs --strict builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… auth

Rename the Filigree bearer credential to the federation-scoped
WEFT_FEDERATION_TOKEN (deconfliction plumbing across the Weft federation).
The loader now prefers it (env then .env) and the operator-facing 401
auth-rejected messages point at the new name. The previous
WARDLINE_FILIGREE_TOKEN is honored as a deprecated fallback (read after the
new name), so existing deployments keep working unchanged.

The original "use Wardline's own name to avoid coupling to a sibling
FILIGREE_* name" rationale dissolves now that the name is federation-scoped
rather than a sibling.

- config.py: WEFT_FEDERATION_TOKEN_ENV primary + WARDLINE_FILIGREE_TOKEN_ENV
  deprecated fallback; _read_token helper; new-name-fully-then-old-name-fully
  precedence; docstring rewritten.
- filigree_emit.py / cli/scan.py: 401 hints name WEFT_FEDERATION_TOKEN.
- tests: config precedence + both-names + legacy fallback; end-to-end
  fallback test in test_install; message assertions updated.
- CHANGELOG [Unreleased] entry; rc2 history lines left verbatim.

Suite 2525 green, ruff/format/mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Auto-regenerated skill-pack artifacts from the SessionStart hooks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ft/wardline layout

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ep); drop waivers/baseline from operator schema; sibling port discovery prefers .weft/<sibling>/

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hs, sibling-endpoint keys, + store_dir/C-9c silent-fallback

- waivers -> .weft/wardline/waivers.yaml (load_project_waivers); add_waiver writes member state
- baseline.yaml/judged.yaml -> .weft/wardline/ via core/paths
- record_bindings -> detect_siblings (detect-only, no config write)
- activate_pack -> operator guidance (packs stay operator-authored)
- doctor reads weft.toml, may create own subtree, reports sibling detection
- MCP config-path defaults -> weft.toml; waiver_add writes member state
- [wardline].store_dir operator override for state-dir relocation (paths.weft_state_dir)
- C-9c: missing/malformed weft.toml -> silent defaults, never hard-fail
- drop sibling-endpoint config keys (hub-pinned, pending); resolver = flag>env>published .weft/<sibling>/ port

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n/judge/fix CLIs); migrate full test suite to weft.toml + .weft/wardline layout

- run.py/scan.py/judge.py/fix.py: config_path default -> weft_config_path (the missed default-path bug all 7 sweep agents flagged)
- ~25 test files migrated: YAML->TOML config fixtures, baseline/judged/waivers paths, detect_siblings, activate_pack guidance, config-URL removal reframed to env/published-port
- glossary line-lock anchors rebound after line shifts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dline.yaml->weft.toml strings, root .gitignore

- docs: configuration.md YAML->TOML rewrite, suppression/judge/attestation/cli/weft/agents/assurance path updates, UPGRADING + CHANGELOG breaking notes
- src: help text / docstrings / comments / analyzer Location -> weft.toml; detect_siblings no-URL hint points at env var + live discovery (not the removed config key)
- .gitignore: drop dead .wardline-cache/ + wardline.yaml; keep .weft/wardline committed, weft.toml tracked

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ttest_key docstring path

wardline.yaml lost its .gitignore entry and got staged by add -A; it is a dead
config file now (its sibling-URL keys are removed/unsupported, nothing reads it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… doctor signal, detect coverage, docs)

- store_dir: confine override under root (absolute-outside-root / ../ escape -> default), matching the writers' safe_project_file confinement; deny a malicious weft.toml a write-redirect (+tests)
- doctor: distinguish absent weft.toml (ok) from present-but-unparseable (error) -> restores the operator signal C-9c silent-fallback removed
- .gitignore: ignore transient sibling .weft/*/ephemeral.port (a committed one could redirect token-bearing emit to a chosen loopback port)
- detect.py docstrings: point operators at the env var, not the removed [wardline.<sibling>].url key
- restore lost coverage for loomweave.yaml discovery (_loomweave_url_from_config / _http_url_from_bind)
- config.py: drop stale 'inert in SP0' comment; resolve_*_url param docstring reframed (shared scan-config bundle parity, not 'reserved')
- waivers.add_waiver: rename misleading config_path param -> path

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_*_url (panel: no dead apparatus)

URL resolution never used pack-trust; removed from both resolve_*_url signatures and
every call site (CLI scan/attest/scan-file-findings + the 3 MCP emitter/client/filer
methods, their callers, and the policy-resolution helpers). config_path stays (passed
positionally; reserved for the pending hub sibling-endpoint key). mypy + full suite
verify no run_scan kwarg was disturbed. Glossary line-lock anchors rebound + stale
state-file path prose in the vocabulary doc corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 7, 2026 01:09

Copilot AI 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.

Pull request overview

This PR advances the Wardline 1.0.0rc4 release candidate by aligning configuration/state layout with the Weft federation conventions, adopting the federation-scoped WEFT_FEDERATION_TOKEN for Filigree auth (with legacy fallback), and hardening the agent/CLI surfaces based on the 2026-06-06 dogfood report (gate clarity, payload controls, and more precise Filigree emit failure classification).

Changes:

  • Adopt federation-scoped WEFT_FEDERATION_TOKEN (preferred) with deprecated fallback to WARDLINE_FILIGREE_TOKEN, and update 401/403/5xx soft-failure reporting for Filigree emit.
  • Complete the Weft config/store consolidation: weft.toml [wardline] replaces wardline.yaml, and suppression state moves under .weft/wardline/ (baseline/judged/waivers) with new path helpers.
  • Improve agent/MCP ergonomics: gate reason/evaluated/migration hint plumbing and scan payload controls (summary_only, max_findings, include_suppressed, truncation signaling, where filtering of agent_summary arrays).

Reviewed changes

Copilot reviewed 106 out of 110 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
uv.lock Rename optional extra from clarion to loomweave for blake3 wiring.
UPGRADING.md Add v1.0 migration guide for weft.toml + .weft/wardline/ state and gate-default change.
tests/unit/test_package.py Loosen version assertion to pin release line rather than rc suffix.
tests/unit/scanner/taint/test_variable_level.py Add branch-local lambda binding regression tests.
tests/unit/scanner/taint/test_provenance_clash.py Update tests to write [wardline] config in weft.toml.
tests/unit/scanner/rules/test_sink_rules.py Add rule-level regression coverage for lambda binding leakage across branches.
tests/unit/scanner/rules/test_contradictory_trust.py Add tests for weft_markers boundary namespace and foreign-marker isolation.
tests/unit/mcp/test_tool_capabilities.py Update sibling URL resolution tests to env/published port (no config keys).
tests/unit/mcp/test_server_suppression.py Switch baseline paths to .weft/wardline/ via baseline_path().
tests/unit/mcp/test_server_security.py Update waiver add + root confinement tests for .weft/ + weft.toml.
tests/unit/mcp/test_server_query_explain.py Add MCP scan payload shrinking tests (where/filtering/caps/truncation/validation).
tests/unit/mcp/test_server_legis_artifact.py New tests pinning MCP legis artifact attach/signing projection behavior.
tests/unit/mcp/test_server_filigree_emit.py Add emit-result status/auth classification and 401/403/5xx messaging tests.
tests/unit/mcp/test_server_attest.py Update pack config test to weft.toml.
tests/unit/loomweave/test_sei_client_wire.py Reformat sign_request expectations (no behavior change).
tests/unit/loomweave/test_client.py Reformat sign_request expectations (no behavior change).
tests/unit/filigree/test_config.py Add tests for WEFT_FEDERATION_TOKEN precedence + legacy fallback.
tests/unit/core/test_waivers.py Add tests for .weft/wardline/waivers.yaml state load/add behavior.
tests/unit/core/test_waiver_add.py Migrate waiver add tests from config to .weft/wardline/waivers.yaml state.
tests/unit/core/test_root_confinement.py Update poisoned config fixture to weft.toml.
tests/unit/core/test_paths.py New tests for canonical Weft paths + store_dir override confinement behavior.
tests/unit/core/test_packs.py Update pack config tests to weft.toml TOML syntax.
tests/unit/core/test_legis_artifact.py Update allow-dirty behavior to emit unsigned dirty-marked artifact.
tests/unit/core/test_judge_run.py Update judged path + judge config to weft.toml; use judged_path().
tests/unit/core/test_filigree_emit.py Add tests for EmitResult status/auth derivation and invariants.
tests/unit/core/test_dossier_assembler.py Update baseline write path to baseline_path().
tests/unit/core/test_decorator_coverage.py Update baseline write path to baseline_path().
tests/unit/core/test_config_toml.py New tests for weft.toml [wardline] parsing + failure modes.
tests/unit/core/test_cli_mcp_parity.py Extend parity test to include gate reason/evaluated/migration_hint.
tests/unit/core/test_baseline_generate.py Update baseline paths and config to weft.toml.
tests/unit/core/test_attest.py Update severity config to TOML + move waivers to .weft/ state.
tests/unit/core/test_assure.py Move waiver setup to .weft/wardline/waivers.yaml state.
tests/unit/core/test_agent_summary.py Add tests for gate reason/evaluated/migration hint + gate-aware next_actions.
tests/unit/cli/test_install.py Update install/scan URL discovery expectations + token threading to WEFT token.
tests/unit/cli/test_install_pack.py Install pack becomes guidance-only; asserts no config is written.
tests/unit/cli/test_doctor.py Doctor no longer writes bindings; detects only and ensures .weft/wardline/ exists.
tests/unit/cli/test_attest_cmd.py Update pack config setup to weft.toml.
tests/unit/cli/test_assure_cmd.py Update waiver setup to .weft/wardline/waivers.yaml state.
tests/docs/test_glossary_vocabulary.py New doc-discipline tests binding glossary completeness + nav + line anchors.
tests/docs/init.py Add docs test package marker.
tests/corpus/test_waiver_discipline.py Read repo waivers from .weft/wardline/waivers.yaml instead of config.
tests/conformance/test_legis_intake_contract.py Small formatting tweak in gate_active computation.
tests/cli/test_scan_summary_vocab.py New tests pinning “active” vocabulary across CLI/MCP/agent surfaces.
tests/cli/init.py Add CLI test package marker.
src/wardline/scanner/rules/contradictory_trust.py Recognize marker modules by prefix set derived from builtin boundary types.
src/wardline/scanner/grammar.py Docstring update: severity overrides now via weft.toml [wardline].
src/wardline/scanner/context.py Docstring update: severity overrides now via weft.toml [wardline].
src/wardline/scanner/analyzer.py Update unused-config finding location path to weft.toml.
src/wardline/mcp/resources.py wardline://config resource now loads weft.toml via weft_config_path().
src/wardline/loomweave/config.py Docstring update: token not read from weft.toml.
src/wardline/install/pack.py Stop writing config; emit operator guidance snippet for enabling packs.
src/wardline/install/doctor.py Doctor updated for weft.toml semantics, detection-only bindings, state dir ensure.
src/wardline/install/detect.py Replace binding persistence with detection-only + published port discovery.
src/wardline/filigree/config.py Prefer WEFT_FEDERATION_TOKEN, fallback to WARDLINE_FILIGREE_TOKEN.
src/wardline/core/waivers.py Move waivers to .weft/wardline/waivers.yaml + add load helper.
src/wardline/core/suppression.py Add severity_gates + gate_breakdown helpers; tighten comparisons.
src/wardline/core/run.py Weft config/state paths, secure gate reason/evaluated + migration hint plumbing.
src/wardline/core/paths.py New single source of truth for weft.toml + .weft/wardline/ and sibling dirs.
src/wardline/core/legis.py Clean-tree-only signing; allow-dirty emits unsigned dirty-marked artifact.
src/wardline/core/judged.py Update judged state docstring to .weft/wardline/judged.yaml.
src/wardline/core/judge_run.py Use weft_config_path() and .weft/wardline/judged.yaml via judged_path().
src/wardline/core/filigree_emit.py EmitResult carries status; derive auth_rejected; shared disabled_reason ladder.
src/wardline/core/errors.py ConfigError docstring updated for weft.toml [wardline].
src/wardline/core/discovery.py Comment update: poisoned config is now weft.toml.
src/wardline/core/config_schema.py Schema now describes weft.toml [wardline]; add store_dir; remove legacy keys.
src/wardline/core/baseline.py Baseline path updated to .weft/wardline/baseline.yaml.
src/wardline/core/attest.py Load config from weft.toml; read waivers from .weft/wardline/waivers.yaml.
src/wardline/core/attest_key.py Docstring update: secrets not in .weft/wardline/.
src/wardline/core/assure.py Waiver rollup now from .weft/wardline/waivers.yaml; doc updates.
src/wardline/core/agent_summary.py Payload shrinking controls + gate-aware next_actions and migration_hint field.
src/wardline/cli/scan_file_findings.py URL resolution now flag/env/published port only; update help text/call shape.
src/wardline/cli/main.py Baseline command paths now under .weft/wardline/.
src/wardline/cli/judge.py Judge reads config from weft.toml; judged output path updated.
src/wardline/cli/install.py Install uses detect_siblings(); pack activation becomes guidance text.
src/wardline/cli/fix.py Fix command reads config from weft.toml via weft_config_path().
src/wardline/cli/file_finding.py Filigree URL help/error text updated (no config key).
src/wardline/cli/attest.py Loomweave URL resolution call shape updated; help text updated.
src/wardline/_version.py Bump version to 1.0.0rc4.
README.md Update CLI summary vocabulary from “new” to “active”.
mkdocs.yml Add finding lifecycle & gate vocabulary page to nav.
docs/reference/cli.md Update docs for weft.toml, .weft/wardline/ paths, and URL resolution semantics.
docs/index.md Update CLI summary vocabulary from “new” to “active”.
docs/guides/weft.md Document sibling URL resolution precedence and detection-only install/doctor.
docs/guides/suppression.md Update suppression layer locations + vocabulary (“active”) + glossary pointer.
docs/guides/loomweave-taint-store.md Update configuration references to weft.toml.
docs/guides/legis-handoff.md Document --allow-dirty dev/tour flow emitting unsigned dirty-marked artifacts.
docs/guides/judge.md Update judged path and config section references for weft.toml.
docs/guides/attestation.md Update secret-storage note to .weft/wardline/.
docs/guides/assurance-posture.md Update waiver source and config references for weft.toml + .weft/ state.
docs/guides/agents.md Update install/doctor semantics + scan vocabulary + scan payload controls section.
docs/getting-started.md Update CLI summary vocabulary from “new” to “active”.
.gitignore Stop ignoring wardline.yaml; add ignore for .weft/*/ephemeral.port and clarify .weft/ policy.
.claude/skills/loomweave-workflow/SKILL.md Document write-gated Loomweave MCP tools in skill docs.
.claude/skills/loomweave-workflow/.fingerprint Update skill fingerprint.
.claude/skills/filigree-workflow/SKILL.md Update error-code names from CLARION→LOOMWEAVE for registry mismatch/out-of-sync.
.agents/skills/loomweave-workflow/SKILL.md Mirror Loomweave skill doc write-gating updates.
.agents/skills/loomweave-workflow/.fingerprint Update skill fingerprint.
.agents/skills/filigree-workflow/SKILL.md Mirror Filigree skill doc CLARION→LOOMWEAVE error-code updates.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/wardline/core/run.py
Comment on lines +357 to +366
baselined = sum(
1
for f in result.findings
if f.kind is Kind.DEFECT
and f.suppressed is SuppressionState.BASELINED
and f.maturity is not Maturity.PREVIEW
and severity_gates(f.severity, fail_on)
)
if not baselined:
return None # tripped by waived/judged only — different escape, not this hint
Comment on lines +31 to +39
def _store_dir_override(root: Path) -> str | None:
"""Read the operator's ``[wardline].store_dir`` override from weft.toml, or None.

Read defensively and silently (C-9c): a missing/malformed weft.toml, a non-table
``[wardline]``, or a non-string ``store_dir`` all fall through to None so the
default location is used. This never raises — store-dir resolution must not be
load-bearing on the shared file parsing cleanly."""
try:
parsed = tomllib.loads(weft_config_path(root).read_text(encoding="utf-8"))

@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: 2a1246266b

ℹ️ 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 last in {"external_boundary", "trust_boundary", "trusted"} and (
fqn.startswith("wardline.decorators.") or fqn.startswith("wardline.decorators.trust.")
):
if last in _MARKER_NAMES and any(fqn.startswith(prefix + ".") for prefix in _MARKER_MODULE_PREFIXES):

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 Match only the real builtin marker exports

When an entity is anchored by one genuine builtin marker, this prefix check also counts any decorator whose FQN merely lives under the same prefix, such as wardline.decorators.evil.trusted or weft_markers.extra.trusted. The builtin provider only recognizes the exact public/implementation exports (P.<name> and P.trust.<name>), so this broader check can report PY-WL-110 for a foreign/non-marker decorator that happens to be nested below a builtin namespace. Reuse the same exact-export predicate here to avoid false contradictory-trust findings.

Useful? React with 👍 / 👎.


cfg = config_mod.load(
_cfg(args, path) or (path / "wardline.yaml"),
_cfg(args, path) or weft_config_path(path),

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 Resolve MCP legis config from the server root

For MCP scan calls that set both a subdirectory path and an explicit config relative to the project root, the actual scan uses _cfg(args, root) earlier, but the legis attachment reloads the config with _cfg(args, path). That points at <subdir>/<config> (or silently falls back to defaults if it is absent), so the attached/signed artifact can compute rule_set_version from a different config than the findings were scanned with. Use the same project-root-relative config resolution as the scan path before building the artifact.

Useful? React with 👍 / 👎.

Comment on lines +1175 to +1177
for name, lam in arm.items():
if pre.get(name) is not lam:
parent[name] = lam

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 Delete branch-local lambda bindings when every arm rebinds

When a lambda name exists before a branch and each mutually-exclusive arm rebinds it to a non-lambda, those arm maps no longer contain the name, so this merge loop never removes the stale entry from parent. After the branch, a call like cb(raw) is still resolved against the pre-branch lambda even though no path leaves cb bound to that lambda, producing new false taint findings. Track removals when all possible arms drop the binding instead of only applying added/changed entries.

Useful? React with 👍 / 👎.

Comment thread src/wardline/core/run.py
Comment on lines +230 to +232
baseline = load_baseline(baseline_path(root))
waivers = WaiverSet(load_project_waivers(root))
judged = load_judged(judged_path(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 Honor strict defaults for state-dir resolution

With --strict-defaults, config_mod.load ignores weft.toml, but these new state-path helpers still read [wardline].store_dir from that same repo file. A checkout can therefore redirect baseline/waiver/judged loading (or point it at malformed state that makes the scan exit 2) even when the caller requested hermetic built-in defaults. The state path needs a strict-defaults path, or run_scan should bypass the override in this mode.

Useful? React with 👍 / 👎.

Comment thread src/wardline/core/config.py Outdated
Comment on lines +155 to +156
except (tomllib.TOMLDecodeError, OSError, UnicodeDecodeError):
return WardlineConfig()

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 Keep explicit config parse errors loud

When a caller passes an explicit --config file that exists but is unparseable (or unreadable), this catch returns built-in defaults, silently dropping the operator's severity/exclude settings and potentially making the scan false-green. The existing missing-file guard in run_scan treats explicit config mistakes as errors; parse/read failures on that explicit path need the same loud behavior while only the implicit shared weft.toml falls back to defaults.

Useful? React with 👍 / 👎.

Comment on lines 308 to +309
if not strict_defaults:
published = _loomweave_published_url(root)
if published is not None:
return published
url = _config_for(
root,
config_path,
trust_local_packs=trust_local_packs,
trusted_packs=trusted_packs,
strict_defaults=strict_defaults,
).loomweave_url
if url and not trust_config_urls and not _is_safe_url(url):
raise ConfigError(
f"Loading Loomweave URL {url!r} from project config is disabled by default for security. "
"Specify the URL via command-line flags, environment variables, or allow local config URLs."
)
return url
return _loomweave_published_url(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 Don't freeze published-port URLs for MCP

When wardline mcp starts while Loomweave is already serving, this new published-port rung returns a concrete URL that the CLI stores in WardlineMCPServer(..., loomweave_url=...); subsequent tool calls then treat it as an explicit flag and never reread .weft/loomweave/ephemeral.port. If the sibling restarts on a new ephemeral port during the MCP session, scans/explain calls keep using the stale port instead of the ADR-044 live publication. Keep published-port discovery inside the per-call server resolver (or preserve the source) so only real flags/env values are frozen.

Useful? React with 👍 / 👎.

John Morrissey and others added 3 commits June 7, 2026 11:47
…ale rc4 docs

Merge-gate fixes for rc4->main.

(1) [major] _read_published_port / _filigree_url_from_project could crash the
scan (and `doctor` detection) on a malicious sibling ephemeral.port. str.isdigit()
is a superset of what int() parses: an all-digit payload over CPython's 4300-digit
cap raises ValueError, and detect.py's utf-8 read also lets Unicode "digit" chars
(²³⁴) through isdigit() to raise in int() — a 3-byte file, so a length bound alone
is insufficient. Both sites now guard int() with try/except (catch -> continue),
restoring the helpers' fail-soft -> None contract. A planted ephemeral.port can no
longer DoS every scan/detect of a repo.

(2) [major] Front-page + CLI docs contradicted the rc4 headline breaking change:
README configuration row repointed wardline.yaml -> weft.toml [wardline]; stale
0.2.0 --version literals bumped to 1.0.0rc4 (getting-started, cli reference x2) to
match runtime.

Tests: over-4300-digit + unicode-isdigit cases added to the loomweave/filigree
published-port malformed params and detect_siblings; red before fix, green after.
Suite 2535 green, ruff/mypy clean, mkdocs --strict builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… store_dir path, gate fields, ascii)

Follow-up to 44c02f0; no behavior change beyond items 3 and 5.

(1) [major] test gap — doctor's present-but-broken weft.toml arm (the only
operator-visibility signal compensating for C-9c's silent fallback) had no test.
Added one asserting a malformed weft.toml surfaces as wardline.config status=="error".

(2) [minor] vacuous test — test_lambda_rebinding_in_try_survives_into_finalbody
passed even without the merge-out fix (the finalbody call is linear after the try
body, no branch join). Rewrote to a genuine try/except post-block join: a rebinding
in the try body must survive for a call AFTER the block, where the except fall-through
does not rebind cb (a clear-then-union merge letting that arm win last would drop the
detection). Non-vacuity pinned by the existing branch-locality control
(test_lambda_binding_is_branch_local_across_try_except: same rebind called inside the
except arm stays INTEGRAL).

(3) [minor] weft_state_dir returned the pre-resolve path, so store_dir = "a/../b"
leaked ".." into user-printed state paths (the confinement check already ran on the
.resolve()d form). Now returns the resolved value; check is byte-identical.

(4) [minor] MCP gate test omitted the agent-facing reason/evaluated/migration_hint
fields (assembled in server.py separately from GateDecision). Now asserted at the MCP
surface (migration_hint required present-and-None: no committed baseline to migrate).

(5) [minor] encoding inconsistency — detect.py read the port file utf-8/replace,
config.py ascii. Aligned detect.py to ascii (correct for the ASCII-int protocol),
mirroring _read_published_port exactly (ascii strict + except OSError/UnicodeDecodeError
+ int-guard); non-ASCII payloads now reject at decode.

Suite 2536 green, ruff/mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ir/config_path notes, dead apparatus, stale wardline.yaml literals)

Follow-up to 58e4346, closing the remaining minor review-panel items:

- Lambda FN (SAE): single-slot dict[str,ast.Lambda] misses a sink-lambda bound
  in a non-last branch arm. Ticketed wardline-383f83fafe (candidate-set fix is
  out of scope); rc4 delta-merge itself is FN-closing/FP-safe, not a regression.
- store_dir dual-parse seam (Arch): config_schema.py comment notes store_dir is
  validated here at config.load() but consumed via a separate raw parse in
  core.paths that bypasses the schema; confinement (not the schema) bounds it.
- config_path dead param (Arch): cli/mcp.py flag-comment for the pending hub
  sibling-endpoint key (weft-a2f4cf95c7); passes None vs the CLI's real path.
- activate_pack dead root param: removed (+ unused Path import); caller updated.
- stale wardline.yaml literals: src never writes that file, so the assertions
  were dead duplicates of the weft.toml guard — removed/renamed across tests;
  test_server_tools YAML->[wardline.autofix] TOML (now valid & read); loomweave
  live test dropped a decorative schema-invalid config-write.
- .env precedence: no change (intended, already documented in filigree/config).

Suite 2536 green, ruff/mypy clean.

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

trust_suppressions = bool(args.get("trust_suppressions") or False)

P2 Badge Reject non-boolean trust_suppressions values

When MCP argument validation is skipped because jsonschema is unavailable (or this handler is exercised directly), a JSON string like "false" is truthy here and enables trust_suppressions. In that context, repository-controlled baseline/waiver/judged entries can clear the gate even though the caller appeared to request the secure default. Use the same strict boolean parsing as _bool_arg so malformed values fail instead of inverting the security posture.

ℹ️ 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".

yaml = require_yaml("loading wardline.yaml")
jsonschema = require_jsonschema("validating wardline.yaml")

jsonschema = require_jsonschema("validating weft.toml [wardline]")

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 Delay jsonschema import until after Wardline table check

When weft.toml exists in a base install without the scanner extra, this eager require_jsonschema runs before the TOML is parsed or the [wardline] table is checked. That means a shared weft.toml that is malformed or only contains another sibling's config raises ConfigError instead of taking the documented/default fallback, breaking zero-dependency callers such as wardline://config or doctor that only need defaults. Parse the file and return defaults for absent/non-Wardline config before importing jsonschema.

Useful? React with 👍 / 👎.

Comment on lines +79 to +81
shown_active = shown_active[: self.max_findings]
shown_suppressed = shown_suppressed[: self.max_findings]
shown_facts = shown_facts[: self.max_findings]

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 Apply max_findings as a shared cap for summary arrays

For MCP scan calls that set max_findings, the top-level findings list is capped to N, but the embedded agent_summary slices active defects, suppressed findings, and engine facts independently. A repo with all three categories can still return up to 3N finding bodies in agent_summary while truncation.findings_returned reports only the top-level count, so the payload control does not actually cap the returned bodies as documented. Use a single budget across these arrays (or derive them from the already-capped display list) so max_findings bounds the full response surface.

Useful? React with 👍 / 👎.

default location is used. This never raises — store-dir resolution must not be
load-bearing on the shared file parsing cleanly."""
try:
parsed = tomllib.loads(weft_config_path(root).read_text(encoding="utf-8"))

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 Honor store_dir from explicit config files

When a command is run with --config other.toml and that file sets [wardline].store_dir, the scan policy is loaded from the explicit file but suppression state is still resolved by reparsing only <root>/weft.toml here. In that setup baseline/waiver/judged records under the configured store are ignored (or new state is written to the default store), so gates and baseline updates disagree with the config the operator explicitly selected. Thread the effective config path/store setting into the state-path helpers instead of always reading the root default.

Useful? React with 👍 / 👎.

…r sweep

Review of the rc4->main diff surfaced one real gap and several latent items.

#1/#2 (Important): an explicit --config pointing at a present-but-malformed
weft.toml silently fell back to default policy via C-9c's fail-soft — a
false-green in the gate. config.load now takes `explicit`: an operator-named
path raises ConfigError on a parse error / non-table [wardline]; an
auto-discovered file warns (visible policy-downgrade) instead of failing
silently before falling back. Threaded explicit= through run_scan, judge_run,
attest, the scan/fix/judge CLIs, and the MCP legis path.

Sweep (latent, no behavior change):
- filigree_disabled_reason derives auth-rejection from status (drops the
  inconsistent reachable/auth_rejected/status triple the type already forbids).
- GateDecision rejects a non-Severity fail_on; AgentSummary rejects negative
  max_findings — matching the EmitResult/GateDecision construction guards.
- legis signed/dirty status read through one shared legis_artifact_outcome
  authority (was re-derived on the CLI and MCP surfaces).
- dropped the dead `config` input from the MCP waiver_add schema; corrected the
  waivers "CLI-written" docstring to "machine-written (MCP)".
- federation-token cross-tier precedence pinned (new-name-in-.env beats
  legacy-name-in-env).

Also: re-pointed the glossary anchors + finding-lifecycle citations to the
shifted source lines, and applied ruff format to two files that were already
drifting against the pinned ruff (pre-existing CI-format-gate failure on rc4).

Suite 2548 green; ruff check/format, mypy, mkdocs --strict all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tachyon-beep

Copy link
Copy Markdown
Collaborator Author

PR-review follow-up — config-load false-green + latent type-rigor sweep (5ebf078)

Ran a 5-aspect review of the rc4 → main diff (code / tests / silent-failure / comments / types). One real gap, rest latent. All applied:

Fixed (the one that mattered):

  • Explicit --config at a present-but-malformed weft.toml no longer silently falls back to default policy — it was a false-green in the gate (C-9c's fail-soft swallowed it). config.load now takes explicit: an operator-named path raises ConfigError on a parse error / non-table [wardline]; an auto-discovered file warns (visible policy-downgrade) before falling back. Threaded explicit= through run_scan, judge_run, attest, the scan/fix/judge CLIs, and the MCP legis + fix paths. (resources.py / MCP config resource stay implicit by design.)

Latent polish (no behavior change):

  • filigree_disabled_reason derives auth-rejection from status (drops the inconsistent reachable/auth_rejected/status triple the EmitResult type already forbids).
  • GateDecision rejects a non-Severity fail_on; AgentSummary rejects a negative max_findings — matching the sibling construction guards.
  • legis signed/dirty read through one shared legis_artifact_outcome authority (was re-derived on the CLI and MCP surfaces); signed now reads signature presence, the authoritative record.
  • dropped the dead config input from the MCP waiver_add schema; corrected the waivers "CLI-written" docstring to "machine-written (MCP)".
  • pinned the federation-token cross-tier precedence (new-name-in-.env beats legacy-name-in-env).

Also: re-pointed glossary anchors + finding-lifecycle citations to shifted source lines; applied ruff format to two files that were already drifting against the pinned ruff (a pre-existing CI-format-gate failure on rc4, unrelated to this change).

Gates: suite 2549 green, ruff check/format, mypy, mkdocs --strict all clean.

Consciously deferred (reviewer Suggestions, not in this pass)

  • No doctor probe for WEFT_FEDERATION_TOKEN (a missing Filigree token already surfaces as a soft 401 at scan time).
  • _read_token doesn't guard OSError on an unreadable-but-present .env (low likelihood; the Loomweave path guards it).
  • The implicit-malformed-weft.toml signal is stderr-only (warnings.warn) — it hits the finding's target (CI false-green + CLI), but an agent calling the MCP scan tool with a broken shared file gets results under default policy with no signal in the JSON-RPC response. Left as operator/CI-only for now; threading a structured warning into the scan response is a larger change than the finding asked for. Flagging for a deliberate call.

@tachyon-beep tachyon-beep merged commit 54b61d9 into main Jun 7, 2026
10 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.

2 participants