From 1086187368fe4e8ffc67c56b5b77acadf8b87f9c Mon Sep 17 00:00:00 2001 From: John Morrissey <544926+tachyon-beep@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:49:49 +1000 Subject: [PATCH] =?UTF-8?q?test(seams):=20harden=20Loomweave/Legis/Filigre?= =?UTF-8?q?e=20seams=20=E2=80=94=20contract=20tests=20+=20degraded-state?= =?UTF-8?q?=20pins=20(PDR-018)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retires the last 3 named production blockers plainweave-side. Test-and-docs only โ€” zero src/plainweave/*.py changes; the seam BEHAVIOR already exists, this freezes/pins it. - #5 Filigree: tests/contracts/test_filigree_contract.py pins open_linked_work as reserved-but-never-emitted (absence is the in-band linked_work_facts_unavailable warning, never empty-but-ok), filigree_issue trace opacity + canonical relations, and the dossier's verdict-free advisory boundary (key + whitelisted-value scan โ€” the dossier legitimately carries lifecycle "approved"/"rejected" values, so the peer-facts scanner doesn't apply). - #4 Legis: behavioral coverage for orphaned_entity_link (previously zero); ADR-006 annotated with the 8-of-11 emission status (active/waived_finding_linked superseded by the dedicated wardline_peer_facts producer; open_linked_work handed off to Filigree); doc-staleness fix (the legis consumer now exists). - #3 Loomweave: tests/loomweave_contract.py validate_loomweave_catalog + degraded golden, pinning the cardinal no-silent-clean invariant โ€” an unavailable adapter never returns a clean-empty page and never advertises positive coverage/pagination while down. Every new test mutation-proven red-first. make ci green (390 tests, 91.18% cov, mypy --strict + ruff); wardline scan 0 active; legis cross-repo preflight oracle stayed green (28 passed โ€” additive, no plainweave obligation). Adversarially reviewed (3 lenses: decorative-test, no-silent-clean/boundary, claim-honesty); all findings applied. Owner-gated Filigree open_linked_work handoff authored (docs/handoffs/2026-06-29-filigree-linked-work-facts.md). ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 15 ++ .../ADR-006-legis-preflight-fact-envelope.md | 20 +++ .../2026-06-29-filigree-linked-work-facts.md | 56 ++++++ docs/product/current-state.md | 12 +- .../PDR-018-seam-hardening-blockers-345.md | 78 +++++++++ tests/contracts/test_contract_fixtures.py | 12 ++ tests/contracts/test_filigree_contract.py | 159 ++++++++++++++++++ tests/contracts/test_loomweave_contract.py | 105 ++++++++++++ .../test_preflight_facts_wire_golden.py | 11 +- .../contracts/loomweave/catalog-degraded.json | 28 +++ tests/loomweave_contract.py | 116 +++++++++++++ tests/test_mcp_read_surface.py | 36 ++++ 12 files changed, 640 insertions(+), 8 deletions(-) create mode 100644 docs/handoffs/2026-06-29-filigree-linked-work-facts.md create mode 100644 docs/product/decisions/PDR-018-seam-hardening-blockers-345.md create mode 100644 tests/contracts/test_filigree_contract.py create mode 100644 tests/contracts/test_loomweave_contract.py create mode 100644 tests/fixtures/contracts/loomweave/catalog-degraded.json create mode 100644 tests/loomweave_contract.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f81e9ec..65d3fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,17 @@ rest of Plainweave. Accepts a SEI or a dotted locator; preserves the no-silent-clean contract (`present`/`absent`/`unavailable` โ€” an identity gap is `unavailable`, never `absent`). +### Added +- **Frozen degraded-state contract for `weft.plainweave.loomweave_catalog.v1`** โ€” the + Loomweave catalog producer's `unavailable`-adapter envelope is now pinned by a structural + validator + golden routed through the same oracle as live output (no-silent-clean: an + unavailable adapter never returns a clean-empty page). Seam-hardening, no behavior change + (PDR-018, production blocker #3). +- **Contract test for the Filigree seam** โ€” pins `open_linked_work` as reserved-but-never- + emitted by the local-only producer (absence is the in-band `linked_work_facts_unavailable` + warning), the `filigree_issue` trace opacity + canonical relations, and the dossier's + advisory boundary (PDR-018, production blocker #5). + ### Fixed - `requirements_enrichment` now drops `rejected` trace links before building the view โ€” a reviewed-and-rejected binding no longer reads as requirement coverage (`present`); a @@ -28,6 +39,10 @@ rest of Plainweave. - `plainweave doctor` Wardline-findings remediation is now root-aware (`cd && wardline scan .` when the inspected root is not the cwd). _(Folded in from a sibling product's contract work.)_ +- ADR-006 now documents that the preflight producer emits 8 of its 11 fact kinds and why the + other three are superseded (dedicated wardline producer) or sibling-gated (Filigree); added + the previously-missing behavioral coverage for the `orphaned_entity_link` fact (PDR-018, + production blocker #4). ## [1.1.0] โ€” 2026-06-27 diff --git a/docs/architecture/decisions/ADR-006-legis-preflight-fact-envelope.md b/docs/architecture/decisions/ADR-006-legis-preflight-fact-envelope.md index d6e2ddf..d3ad4eb 100644 --- a/docs/architecture/decisions/ADR-006-legis-preflight-fact-envelope.md +++ b/docs/architecture/decisions/ADR-006-legis-preflight-fact-envelope.md @@ -84,6 +84,26 @@ orphaned_entity_link untraced_change ``` +### Emission status (annotated 2026-06-29, PDR-018 โ€” seam hardening) + +The local-only producer emits **8 of the 11** kinds above. Three are deliberately +**not emitted** by this producer, and their absence is reported in-band as +`info`/`freshness: unavailable` warnings (`linked_work_facts_unavailable`, +`finding_facts_unavailable`) โ€” never as an empty-but-ok fact list (no-silent-clean): + +- `active_finding_linked`, `waived_finding_linked` โ€” **superseded** by the dedicated + `weft.plainweave.wardline_peer_facts.v1` producer (PDR-014), which surfaces the same + local `.wardline/*-findings.jsonl` data. Wiring them into the preflight envelope as + well is an owner-gated ADR fork, intentionally **not** taken (PDR-018). +- `open_linked_work` โ€” **sibling-gated**: no in-grant local Filigree source exists + (`.filigree/` is a Filigree-owned DB; the live `entity_association` read would break + `authority_boundary.live_peer_calls=false`). Handed off as + `docs/handoffs/2026-06-29-filigree-linked-work-facts.md`. + +Reversal trigger: if a boundary-clean local Filigree facts artifact (mirroring +`.wardline/*.jsonl`) lands, `open_linked_work` becomes emittable in-grant and this +fork reopens. + ## Related Decisions - ADR-001: Plainweave authority boundary. diff --git a/docs/handoffs/2026-06-29-filigree-linked-work-facts.md b/docs/handoffs/2026-06-29-filigree-linked-work-facts.md new file mode 100644 index 0000000..7920587 --- /dev/null +++ b/docs/handoffs/2026-06-29-filigree-linked-work-facts.md @@ -0,0 +1,56 @@ +# Peer prompt โ€” Filigree: emit a local linked-work facts artifact for Plainweave + +**To:** the Filigree maintainer / implementing agent. +**From:** Plainweave (owner-gated sibling handoff). +**Date:** 2026-06-29 (PDR-018, seam hardening). +**Status:** PROPOSED โ€” owner-gated. Do **not** implement on the Plainweave side; this is +Filigree-side work plus a small Plainweave adapter once the artifact exists. + +## Why + +Plainweave's Legis preflight producer (`weft.plainweave.preflight_facts.v1`, ADR-006) +reserves an `open_linked_work` fact kind: "for a scoped requirement/gap, what Filigree +issues are open and linked?". Plainweave **cannot emit it today** and honestly reports the +gap in-band as the `linked_work_facts_unavailable` warning (no-silent-clean). It cannot be +emitted in-grant because: + +- Plainweave never calls a sibling live: `authority_boundary.live_peer_calls = false`. The + sanctioned `entity_association_list_by_entity` (ADR-029) is a live MCP/HTTP call, so it + would break that boundary. +- `.filigree/` is a Filigree-owned SQLite DB. Direct reads would couple Plainweave to + Filigree's internal schema โ€” exactly the coupling the thin-member doctrine forbids. + +Unlike Wardline (whose append-only `.wardline/*-findings.jsonl` Plainweave already adapts +in-grant), Filigree exposes **no local, boundary-clean facts artifact** Plainweave can read. + +## The ask (Filigree-side) + +Emit a **local, append-only or snapshot facts artifact** โ€” mirroring `.wardline/*.jsonl` โ€” +that Plainweave can read read-only with no live call. Suggested: `.filigree/linked-work.jsonl` +(or a documented export command) with, per row, the stable identity Plainweave keys on plus +the issue's open/closed lifecycle facts. Concretely, enough to answer "for requirement R / +gap G, which Filigree issues are open and link to it": + +- the linked entity/requirement/gap identity (a SEI or the `filigree_issue` / `gap` ids + Plainweave already stores as opaque trace refs โ€” see `service.py` canonical relations + `('filigree_issue','implements_work_for','requirement_version')` and + `('filigree_issue','resolves_gap','gap')`), +- issue id, status/lifecycle (open vs closed/resolved), and a freshness/observed-at stamp. + +Facts only โ€” **no verdicts**. Filigree owns the work lifecycle; Plainweave only surfaces the +linkage as advisory context. Plainweave will **never** create or move Filigree work from +this seam (the `gap_create_work` write path stays unimplemented; it would mutate a sibling). + +## Then (Plainweave-side, once the artifact exists โ€” a separate owner-gated bid) + +A `FiligreeAdapter` reading that artifact in-grant (the Wardline-adapter pattern), wiring real +`open_linked_work` facts into `preflight_facts.v1`, with the `linked_work_facts_unavailable` +warning degrading to a `filigree_linked_work_absent` code only when the artifact is genuinely +absent โ€” and a frozen contract test (mirroring the wardline/warpline/loomweave contract tests +landed in PDR-018). + +## Reversal trigger (recorded in ADR-006) + +If this boundary-clean local artifact lands, `open_linked_work` becomes emittable in-grant and +the ADR-006 emission fork (PDR-018) reopens. Until then, the in-band +`linked_work_facts_unavailable` warning is the correct honest posture. diff --git a/docs/product/current-state.md b/docs/product/current-state.md index 48e0c31..7c71f10 100644 --- a/docs/product/current-state.md +++ b/docs/product/current-state.md @@ -1,4 +1,4 @@ -# Plainweave Current State Checkpoint: 2026-06-28 (PDR-016; prior PDR-015) ยท (commit recorded below) +# Plainweave Current State Checkpoint: 2026-06-29 (PDR-018; prior PDR-016) ยท (branch feat/seam-hardening-blockers-345) ## The bet right now @@ -65,6 +65,10 @@ north-star (coverage completeness) unchanged โ€” owner-/sibling-gated. **Owner calls on the release escalation** โ€” finalize the `1.2.0` CHANGELOG version/date, reconcile/retire the `release/1.2.0` branch against `main`, then the held PyPI publish. Then close the two **Lacuna-tour prerequisites** (fix the packaging bug โ†’ install plainweave โ†’ -clean-tree `make tour`). Then continue **harden + build** (remaining production blockers: -Loomweave-owned identity resolution, Legis fact emission, Filigree contract tests) or pivot to -**coverage-completeness** if the owner wants north-star movement. +clean-tree `make tour`). All 5 named production blockers are now retired plainweave-side โ€” +**PDR-018 closed the last three** (#3 Loomweave identity / #4 Legis fact emission / #5 +Filigree contract tests) as test-and-docs-only hardening (390 tests, 91.18% cov; legis +cross-repo oracle stayed green; zero `src/` changes). The only open seam item is the +owner-gated Filigree `open_linked_work` handoff +(`docs/handoffs/2026-06-29-filigree-linked-work-facts.md`). Next: pivot to +**coverage-completeness** if the owner wants north-star movement, or finalize the release. diff --git a/docs/product/decisions/PDR-018-seam-hardening-blockers-345.md b/docs/product/decisions/PDR-018-seam-hardening-blockers-345.md new file mode 100644 index 0000000..f747168 --- /dev/null +++ b/docs/product/decisions/PDR-018-seam-hardening-blockers-345.md @@ -0,0 +1,78 @@ +# PDR-018: Seam hardening โ€” production blockers #3/#4/#5 retired plainweave-side (contract tests + test-hardening) + +Date: 2026-06-29 Status: accepted Author: agent:claude-product-owner +Owner sign-off: EXPLICIT (owner set the bid โ€” "do the seam hardening โ€ฆ then get it out the door" โ€” and authorized subagent-driven multi-agent delivery via "ultracode"/"ultrathink") +Related: PDR-014 (peer facts; retired blockers 1/2 + Warpline/Wardline contract tests), PDR-009 (no-silent-clean / no-vanity-metric), PDR-011 (doctor federation parity), ADR-006 (Legis preflight fact envelope), ADR-005 (Loomweave SEI consumer contract) + +## Context + +PDR-014 retired 3 of the 5 named production blockers. This bid retires the remaining +three โ€” (3) Loomweave-owned identity resolution, (4) Legis fact emission, (5) Filigree +contract tests โ€” under the harden-and-ship posture. A scoping workflow (3 investigators + +adversarial challenge per blocker + contract-template auditor) established the **honest +size** of each: the seam BEHAVIOR was already built and tested for #3 and #4; the genuine +gaps were contract-test/parity artifacts and one zero-coverage fact kind. The adversarial +pass rejected two unsafe over-scopes (suppressing the Legis `*_unavailable` warnings โ€” a +silent-clean hole; a dedicated Filigree validator module โ€” Filigree emits no `.v1` payload). + +## The call + +**Plainweave-side, test-and-docs only โ€” ZERO `src/` production changes** (the seam behavior +already exists; this freezes and pins it). Delivered via inline TDD with every new test +proven red-first against a transient producer mutation (anti-decorative), then reverted. + +- **#5 Filigree contract tests โ€” RETIRED.** New `tests/contracts/test_filigree_contract.py` + pins: `open_linked_work` is never emitted by the local-only producer (absence is the + in-band `linked_work_facts_unavailable` warning, never empty-but-ok); the `filigree_issue` + `implements_work_for` relation (previously untested) is canonical and stored as an opaque + pointer; a non-canonical filigree relation is a VALIDATION error; the dossier introduces no + gate/decision/enforcement KEY. The scope-independent presence of `linked_work_facts_unavailable` + on an EMPTY scope is pinned by an extension to the existing empty-scope preflight test. + (Finding: the peer-facts value-token scanner does NOT apply to the dossier, which + legitimately carries lifecycle `"approved"`/`"rejected"` VALUES โ€” the meaningful invariant + is the absence of a verdict KEY.) +- **#4 Legis fact emission โ€” degraded-state already done; test-hardening landed; remaining + kinds handed-off / superseded (NOT "all kinds flowing").** The producer's explicit + degraded-state (`live_diff_resolution_unavailable`, `freshness: partial`, `requirement_nearby` + basis) was already present and tested. Added the missing behavioral coverage for + `orphaned_entity_link` (emitted but previously zero-coverage). Of ADR-006's 11 fact kinds the + producer emits 8; the three unemitted are now PROVENANCED in ADR-006 (annotation): + `active_finding_linked`/`waived_finding_linked` are **superseded** by the dedicated + `weft.plainweave.wardline_peer_facts.v1` producer (PDR-014) โ€” wiring them into the preflight + envelope is an owner-gated fork intentionally NOT taken; `open_linked_work` is **sibling-gated** + (handed off, below). Fixed a stale doc claim (the Legis consumer now exists). +- **#3 Loomweave identity resolution โ€” behavior already done+tested; PDR-014-parity contract + landed.** The live HTTP resolve + capability probe + closed-vocab degraded codes + SEI ยง8 + oracle were already implemented and tested. Added the missing PRODUCER-side contract: + `tests/loomweave_contract.py::validate_loomweave_catalog` + a degraded golden + (`tests/fixtures/contracts/loomweave/catalog-degraded.json`), routing the committed golden + AND live producer output through one validator so they cannot diverge. The cardinal + invariant pinned: an `unavailable` adapter never returns a clean-empty page (empty `items` + must carry a non-empty `degraded[]`). + +## Evidence + +`make ci` green: **390 tests** (up from 378), **91.18% coverage** (up from 91.14%), mypy +--strict + ruff clean. `wardline scan` clean (0 active). Cross-repo: legis's vendored preflight +oracle stays green (**28 passed**) โ€” additive plainweave-side test/doc changes created no +legis obligation (legis pins the empty `commit_range` scenario; plainweave pins the populated +one). Every new test mutation-verified red-before-green. Zero `src/plainweave/*.py` changes. + +## Scope explicitly NOT taken (honesty) + +- The preflight wire-golden was NOT enriched with `orphaned_entity_link`/`requirement_nearby`; + the behavioral test closes the coverage gap, and the wire-golden remains one representative + scenario โ€” avoiding a byte-pin re-vendor and any cross-repo churn for no added guarantee. +- No live Filigree join, no `gap_create_work` write path (would mutate a sibling; spine-prohibited). + +## Sibling follow-on (OWNER-GATED โ€” handed off, not done) + +`docs/handoffs/2026-06-29-filigree-linked-work-facts.md`: a peer prompt asking Filigree to emit +a local, boundary-clean linked-work facts artifact (mirroring `.wardline/*.jsonl`) that +Plainweave could adapt in-grant to emit real `open_linked_work` facts. + +## Reversal trigger + +If a boundary-clean local Filigree facts artifact lands, `open_linked_work` becomes emittable +in-grant and the ADR-006 emission fork reopens (a `FiligreeAdapter` + its contract test, a +separate owner-gated bid). Recorded in ADR-006's emission-status annotation. diff --git a/tests/contracts/test_contract_fixtures.py b/tests/contracts/test_contract_fixtures.py index 482f6d0..a437ac3 100644 --- a/tests/contracts/test_contract_fixtures.py +++ b/tests/contracts/test_contract_fixtures.py @@ -5,6 +5,7 @@ from typing import Any, cast from tests.intent_coverage_contract import validate_intent_coverage +from tests.loomweave_contract import validate_loomweave_catalog from tests.preflight_contract import validate_preflight_facts from tests.wardline_contract import validate_wardline_peer_facts from tests.warpline_contract import validate_requirements_enrichment @@ -73,6 +74,7 @@ "intent/intent-coverage.json", "loomweave/identity-resolve.json", "loomweave/identity-sei.json", + "loomweave/catalog-degraded.json", "mcp/side-effect-metadata.json", "mcp/tool-inventory.json", "mcp/resource-inventory.json", @@ -396,6 +398,16 @@ def test_preflight_facts_fixture_contract() -> None: validate_preflight_facts({key: value for key, value in fixture.items() if key != "schema"}) +def test_loomweave_catalog_degraded_fixture_contract() -> None: + fixture = load_fixture("loomweave/catalog-degraded.json") + + assert fixture["schema"] == "weft.plainweave.loomweave_catalog.v1" + # The degraded envelope is validated through the SAME structural validator as live + # output (test_loomweave_contract.py), so the golden and the running tool cannot + # diverge without one test or the other failing. + validate_loomweave_catalog({key: value for key, value in fixture.items() if key != "schema"}) + + def test_intent_coverage_fixture_contract() -> None: fixture = load_fixture("intent/intent-coverage.json") diff --git a/tests/contracts/test_filigree_contract.py b/tests/contracts/test_filigree_contract.py new file mode 100644 index 0000000..675661a --- /dev/null +++ b/tests/contracts/test_filigree_contract.py @@ -0,0 +1,159 @@ +"""Contract test for Plainweave's FILIGREE seam (production blocker #5). + +Unlike the warpline/wardline/legis seams, Plainweave emits NO dedicated Filigree +``.v1`` payload: there is no filigree adapter, no ``filigree_*`` MCP tool, no +``weft.plainweave.filigree*.v1`` envelope. Plainweave's Filigree seam is two +plainweave-OWNED representations embedded in existing envelopes, plus a +deliberately-absent live join: + + (A) ``preflight_facts.v1``: ``open_linked_work`` is a RESERVED fact kind that the + local-only producer NEVER emits; Filigree linked-work absence is reported + in-band by the ``linked_work_facts_unavailable`` warning (no-silent-clean). + (B) ``requirement_dossier`` / ``trace_link``: ``filigree_issue`` is a canonical + TraceRef kind with two canonical relations; Plainweave stores an OPAQUE pointer + to a Filigree issue id and never reads/resolves/mutates live Filigree. + +This pins the honest representation, mirroring the warpline/wardline contract tests, +WITHOUT taking on Filigree's work lifecycle and WITHOUT a live Filigree call. The +live linked-work join (turning the warning into real ``open_linked_work`` facts) and +the ``gap_create_work`` write path are sibling-gated; see +``docs/handoffs/2026-06-29-filigree-linked-work-facts.md``. + +The scope-INDEPENDENT presence of ``linked_work_facts_unavailable`` (invariant 1) is +pinned by an extension to +``tests/test_mcp_read_surface.py::test_mcp_preflight_freshness_is_unavailable_for_empty_project_scope`` โ€” +the two files together are the Filigree contract test. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any, cast + +import pytest +from tests.test_mcp_read_surface import approve_requirement, service_for + +from plainweave.errors import ErrorCode, PlainweaveError +from plainweave.mcp_surface import PlainweaveMcpSurface +from plainweave.models import TraceRef + +# Governance gate/decision tokens the advisory dossier must never introduce โ€” checked both +# as KEYS and as VALUES. NOTE: unlike the peer-facts producers, the dossier legitimately +# carries requirement-lifecycle VALUES ("approved"/"rejected") and trace authority/state +# ("accepted") as DOMAIN state โ€” those are not governance verdicts โ€” so the off-the-shelf +# peer-facts value-token scanner (assert_no_preflight_verdicts) would false-positive here. +# Instead we whitelist exactly those lifecycle/authority values and still reject every OTHER +# governance token appearing as a value, so a verdict expressed as a value (e.g. "block") +# under a non-verdict key cannot slip through. +_VERDICT_KEYS = {"allow", "allowed", "block", "blocked", "verdict", "decision", "gate", "enforcement"} +_DOSSIER_LIFECYCLE_VALUES = {"approved", "rejected", "accepted"} +_VERDICT_VALUE_TOKENS = { + "allow", + "allowed", + "block", + "blocked", + "block_candidate", + "deny", + "denied", + "approved", + "rejected", + "pass_fail", + "verdict", +} - _DOSSIER_LIFECYCLE_VALUES + + +def data(envelope: dict[str, Any]) -> dict[str, Any]: + return cast(dict[str, Any], envelope["data"]) + + +def _assert_dossier_is_verdict_free(value: object) -> None: + """Reject governance verdicts by KEY anywhere and by VALUE (except the whitelisted + requirement-lifecycle / trace-authority values that are legitimate dossier state).""" + if isinstance(value, dict): + offenders = sorted(set(value) & _VERDICT_KEYS) + assert not offenders, f"verdict-like key in dossier: {offenders}" + for item in value.values(): + _assert_dossier_is_verdict_free(item) + elif isinstance(value, list): + for item in value: + _assert_dossier_is_verdict_free(item) + elif isinstance(value, str): + assert value.strip().lower() not in _VERDICT_VALUE_TOKENS, f"verdict-like value in dossier: {value}" + + +def test_filigree_linked_work_absence_is_warned_not_silently_clean(tmp_path: Path) -> None: + """No-silent-clean. The LOAD-BEARING pin is that ``linked_work_facts_unavailable`` is + present on every scope (it is the in-band signal of Filigree linked-work absence). The + second assertion (no ``open_linked_work`` fact) is a FORWARD-GUARD: ``open_linked_work`` + is reserved vocab the local-only producer never constructs today, so it must never appear + as an empty-but-ok fact list when a future live Filigree join lands (sibling-gated).""" + service = service_for(tmp_path) + requirement_id = approve_requirement(service) + surface = PlainweaveMcpSurface(tmp_path) + + for envelope in ( + surface.plainweave_preflight_facts_get(scope_kind="project"), + surface.plainweave_preflight_facts_get(scope_kind="explicit_requirements", requirement_ids=[requirement_id]), + surface.plainweave_preflight_facts_get(scope_kind="pending_diff"), + ): + preflight = data(envelope) + facts = cast(list[dict[str, Any]], preflight["facts"]) + assert all(fact["kind"] != "open_linked_work" for fact in facts) + warning_codes = {warning["code"] for warning in preflight["warnings"]} + assert "linked_work_facts_unavailable" in warning_codes + + +def test_filigree_issue_implements_work_for_trace_is_canonical_and_opaque(tmp_path: Path) -> None: + """The ``implements_work_for`` relation (previously untested; only ``resolves_gap`` + was exercised) is accepted and the Filigree issue id is stored as an OPAQUE + pointer surfaced verbatim in the dossier โ€” no live Filigree resolution.""" + service = service_for(tmp_path) + requirement_id = approve_requirement(service) + + service.propose_trace_link( + TraceRef("filigree_issue", "PW-123"), + "implements_work_for", + TraceRef("requirement_version", f"{requirement_id}@1"), + actor="agent:codex", + ) + + dossier = service.requirement_dossier(requirement_id) + filigree_traces = [item for item in dossier.traces.items if item.from_ref.kind == "filigree_issue"] + assert len(filigree_traces) == 1 + assert filigree_traces[0].from_ref.id == "PW-123" # opaque, never resolved/rewritten + assert filigree_traces[0].relation == "implements_work_for" + + +def test_noncanonical_filigree_relation_is_rejected(tmp_path: Path) -> None: + """Only the two canonical ``filigree_issue`` relations are accepted; a + non-canonical relation is a VALIDATION error, never silently stored.""" + service = service_for(tmp_path) + requirement_id = approve_requirement(service) + + with pytest.raises(PlainweaveError) as exc_info: + service.propose_trace_link( + TraceRef("filigree_issue", "PW-123"), + "satisfies", # canonical for loomweave_entity, NOT for filigree_issue + TraceRef("requirement_version", f"{requirement_id}@1"), + actor="agent:codex", + ) + assert exc_info.value.code == ErrorCode.VALIDATION + + +def test_dossier_carrying_a_filigree_trace_is_verdict_free(tmp_path: Path) -> None: + """The advisory boundary: a dossier surfacing a Filigree trace introduces NO governance + verdict โ€” by key OR by value โ€” beyond the whitelisted requirement-lifecycle/authority + values that are legitimate domain state (see the _VERDICT_VALUE_TOKENS note above).""" + service = service_for(tmp_path) + requirement_id = approve_requirement(service) + service.propose_trace_link( + TraceRef("filigree_issue", "PW-123"), + "implements_work_for", + TraceRef("requirement_version", f"{requirement_id}@1"), + actor="agent:codex", + ) + + envelope = PlainweaveMcpSurface(tmp_path).plainweave_requirement_dossier_get(requirement_id) + + assert envelope["ok"] is True + _assert_dossier_is_verdict_free(envelope) diff --git a/tests/contracts/test_loomweave_contract.py b/tests/contracts/test_loomweave_contract.py new file mode 100644 index 0000000..3fc8865 --- /dev/null +++ b/tests/contracts/test_loomweave_contract.py @@ -0,0 +1,105 @@ +"""Contract test for ``weft.plainweave.loomweave_catalog.v1`` (production blocker #3: +explicit degraded peer-state envelope for the live Loomweave adapter). + +The Loomweave identity-resolution + catalog BEHAVIOR is already implemented and tested +(live HTTP resolve + capability probe + closed-vocab degraded codes + the SEI ยง8 oracle ++ adapter/producer degraded tests). The remaining PDR-014-parity gap was the absence of +a PRODUCER-side contract pinning the catalog envelope's DEGRADED state. This freezes it, +mirroring the wardline/preflight contract tests: the committed golden and the live +producer are validated through the SAME structural validator, so they cannot diverge. +No production code changes โ€” the behavior already exists; this is the parity contract. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, cast + +import pytest +from tests.loomweave_contract import assert_no_loomweave_verdicts, validate_loomweave_catalog +from tests.loomweave_test_utils import seed_loomweave_catalog + +from plainweave.mcp_surface import PlainweaveMcpSurface +from plainweave.store import migrate + +FIXTURE = Path(__file__).resolve().parents[1] / "fixtures" / "contracts" / "loomweave" / "catalog-degraded.json" + + +def _surface(tmp_path: Path) -> PlainweaveMcpSurface: + migrate(tmp_path / ".plainweave" / "plainweave.db", project_key="AUTH") + return PlainweaveMcpSurface(tmp_path) + + +def _data(envelope: dict[str, Any]) -> dict[str, Any]: + return cast(dict[str, Any], envelope["data"]) + + +def _minimal_unavailable() -> dict[str, Any]: + return { + "items": [], + "limit": 50, + "offset": 0, + "has_more": False, + "next_offset": None, + "adapter_status": { + "status": "unavailable", + "db_path": ".weft/loomweave/loomweave.db", + "http_url": None, + "identity_http": "not_configured", + "sei_supported": False, + }, + "degraded": [{"code": "loomweave_db_missing", "message": "missing"}], + "coverage": { + "public_surface_tags": [], + "present_tags": [], + "absent_tags": [], + "complete": False, + "present_plugins": [], + }, + } + + +def test_validator_accepts_a_minimal_unavailable_payload() -> None: + validate_loomweave_catalog(_minimal_unavailable()) + + +def test_validator_rejects_a_silent_clean_unavailable_page() -> None: + """The cardinal violation: an unavailable adapter returning an empty page with NO + degraded reason โ€” a clean-empty read that hides the degradation.""" + payload = _minimal_unavailable() + payload["degraded"] = [] + with pytest.raises(AssertionError): + validate_loomweave_catalog(payload) + + +def test_validator_rejects_a_verdict_token() -> None: + with pytest.raises(AssertionError): + assert_no_loomweave_verdicts({"status": "blocked"}) + + +def test_committed_degraded_golden_matches_the_validator() -> None: + fixture = json.loads(FIXTURE.read_text(encoding="utf-8")) + assert fixture["schema"] == "weft.plainweave.loomweave_catalog.v1" + # Validated through the SAME structural validator as live output, so the golden and + # the running tool cannot diverge without one test or the other failing. + validate_loomweave_catalog({key: value for key, value in fixture.items() if key != "schema"}) + + +def test_live_unavailable_adapter_envelope_is_valid_and_in_band_degraded(tmp_path: Path) -> None: + """The real producer over a root with no Loomweave db: an unavailable adapter, an + empty page, and a degraded reason carried in-band (no silent-clean).""" + data = _data(_surface(tmp_path).plainweave_loomweave_catalog_list(limit=50, offset=0)) + validate_loomweave_catalog(data) + assert data["adapter_status"]["status"] == "unavailable" + assert data["items"] == [] + assert data["degraded"], "unavailable adapter must report a degraded reason in-band" + + +def test_live_healthy_catalog_envelope_is_valid(tmp_path: Path) -> None: + surface = _surface(tmp_path) + seed_loomweave_catalog(tmp_path) + data = _data(surface.plainweave_loomweave_catalog_list(limit=50, offset=0)) + validate_loomweave_catalog(data) + assert data["adapter_status"]["status"] == "available" + assert data["items"] diff --git a/tests/contracts/test_preflight_facts_wire_golden.py b/tests/contracts/test_preflight_facts_wire_golden.py index 711208a..08adbb3 100644 --- a/tests/contracts/test_preflight_facts_wire_golden.py +++ b/tests/contracts/test_preflight_facts_wire_golden.py @@ -4,10 +4,13 @@ ``tests/fixtures/contracts/legis/preflight-facts.json`` is the preflight-facts ``schema + data`` payload plainweave emits from ``PlainweaveMcpSurface.plainweave_preflight_facts_get`` โ€” the producer named in -ADR-006 (Status: Accepted). Legis is the intended CONSUMER of this envelope, but -the consumer side does NOT exist yet and legis is ringfenced, so this row is -PRODUCER-SIDE ONLY: it freezes plainweave's own produced bytes and ties them to -the live producer. There is no consumer oracle and no cross-repo drift check. +ADR-006 (Status: Accepted). Legis is the CONSUMER of this envelope. As of +2026-06-29 a legis-side consumer + constructed oracle of this contract exists, but +per the federation enrich-only discipline that read ships solo and creates NO +plainweave obligation โ€” plainweave OWNS the contract and legis conforms to it. This +row therefore stays PRODUCER-SIDE ONLY by design: it freezes plainweave's own +produced bytes and ties them to the live producer; plainweave runs no cross-repo +drift gate against the legis copy (legis re-pins on its side). PLAINWEAVE IS THE AUTHORITY for this seam โ€” it OWNS the preflight-facts shape via ``PlainweaveMcpSurface.plainweave_preflight_facts_get``. The protection is a diff --git a/tests/fixtures/contracts/loomweave/catalog-degraded.json b/tests/fixtures/contracts/loomweave/catalog-degraded.json new file mode 100644 index 0000000..bc0b22b --- /dev/null +++ b/tests/fixtures/contracts/loomweave/catalog-degraded.json @@ -0,0 +1,28 @@ +{ + "schema": "weft.plainweave.loomweave_catalog.v1", + "items": [], + "limit": 50, + "offset": 0, + "has_more": false, + "next_offset": null, + "adapter_status": { + "status": "unavailable", + "db_path": ".weft/loomweave/loomweave.db", + "http_url": null, + "identity_http": "not_configured", + "sei_supported": false + }, + "degraded": [ + { + "code": "loomweave_db_missing", + "message": "Loomweave database is missing: .weft/loomweave/loomweave.db" + } + ], + "coverage": { + "public_surface_tags": ["cli-command", "entry-point", "exported-api", "http-route"], + "present_tags": [], + "absent_tags": [], + "complete": false, + "present_plugins": [] + } +} diff --git a/tests/loomweave_contract.py b/tests/loomweave_contract.py new file mode 100644 index 0000000..532c24e --- /dev/null +++ b/tests/loomweave_contract.py @@ -0,0 +1,116 @@ +"""Single source of truth for the ``weft.plainweave.loomweave_catalog.v1`` shape. + +Mirrors ``tests/wardline_contract.py`` so the committed degraded golden and the live +``plainweave_loomweave_catalog_list`` producer cannot drift in shape or degraded-state +invariants (both are run through this one structural validator). The catalog envelope +carries NO ``severity`` field, so the no-verdict scan uses the warpline-style scanner +(no severity allowlist branch). + +The cardinal invariant is no-silent-clean: when the Loomweave adapter is +``unavailable`` (db/schema missing) the empty page MUST carry a non-empty +``degraded[]`` reason โ€” an empty ``items`` list must never read as a clean +"nothing here". The Loomweave identity-resolution + catalog BEHAVIOR is already +implemented and tested (live HTTP resolve + capability probe + closed-vocab degraded +codes + the SEI ยง8 oracle + adapter/producer degraded tests); this validator freezes +the PRODUCER-side contract that was the remaining PDR-014-parity gap. +""" + +from __future__ import annotations + +from typing import Any + +LOOMWEAVE_ADAPTER_STATUSES = {"available", "unavailable"} +LOOMWEAVE_CATALOG_DATA_KEYS = { + "items", + "limit", + "offset", + "has_more", + "next_offset", + "adapter_status", + "degraded", + "coverage", +} +LOOMWEAVE_ADAPTER_STATUS_KEYS = {"status", "db_path", "http_url", "identity_http", "sei_supported"} +LOOMWEAVE_COVERAGE_KEYS = {"public_surface_tags", "present_tags", "absent_tags", "complete", "present_plugins"} +LOOMWEAVE_ITEM_KEYS = { + "sei", + "locator", + "kind", + "tags", + "source", + "content_hash", + "content_hash_at_attach", + "public_signal", + "briefing_blocked", + "lineage_status", + "freshness", + "observed_at", + "degraded", + "signals", +} + +_VERDICT_KEYS = {"allow", "allowed", "block", "blocked", "verdict", "decision", "gate", "enforcement"} +_VERDICT_VALUE_TOKENS = { + "allow", + "allowed", + "block", + "blocked", + "block_candidate", + "deny", + "denied", + "approved", + "rejected", + "pass_fail", + "verdict", +} + + +def assert_no_loomweave_verdicts(value: object) -> None: + """Reject gate semantics by key and by string value (the catalog has no severity field).""" + if isinstance(value, dict): + assert _VERDICT_KEYS.isdisjoint(value), f"verdict-like key in {sorted(value)}" + for item in value.values(): + assert_no_loomweave_verdicts(item) + elif isinstance(value, list): + for item in value: + assert_no_loomweave_verdicts(item) + elif isinstance(value, str): + assert value.strip().lower() not in _VERDICT_VALUE_TOKENS, f"verdict-like value: {value}" + + +def validate_loomweave_catalog(payload: dict[str, Any]) -> None: + """Structurally validate a loomweave-catalog *data* payload (no envelope wrapper).""" + assert set(payload) == LOOMWEAVE_CATALOG_DATA_KEYS, f"section drift: {sorted(payload)}" + + status_block = payload["adapter_status"] + assert set(status_block) == LOOMWEAVE_ADAPTER_STATUS_KEYS, f"adapter_status drift: {sorted(status_block)}" + assert status_block["status"] in LOOMWEAVE_ADAPTER_STATUSES + + degraded = payload["degraded"] + assert isinstance(degraded, list) + for entry in degraded: + assert {"code", "message"}.issubset(entry), f"degraded entry drift: {sorted(entry)}" + + items = payload["items"] + assert isinstance(items, list) + + coverage = payload["coverage"] + assert set(coverage) == LOOMWEAVE_COVERAGE_KEYS, f"coverage drift: {sorted(coverage)}" + assert isinstance(coverage["complete"], bool) + + # The cardinal no-silent-clean invariant: an unavailable adapter never returns a + # clean-empty page โ€” it reports its degradation in-band AND never advertises any + # positive coverage/pagination signal that would read as "we have data" while down. + if status_block["status"] == "unavailable": + assert items == [], "unavailable adapter must not enumerate items" + assert degraded, "unavailable adapter must report a degraded reason in-band (no silent-clean)" + assert coverage["complete"] is False, "unavailable adapter must not claim complete coverage" + assert coverage["present_tags"] == [], "unavailable adapter must not claim present tags" + assert coverage["present_plugins"] == [], "unavailable adapter must not claim present plugins" + assert payload["has_more"] is False, "unavailable adapter must not claim more pages" + assert payload["next_offset"] is None, "unavailable adapter must not advertise a next page" + + for item in items: + assert set(item) == LOOMWEAVE_ITEM_KEYS, f"item key drift: {sorted(item)}" + + assert_no_loomweave_verdicts(payload) diff --git a/tests/test_mcp_read_surface.py b/tests/test_mcp_read_surface.py index af48e93..9479ba0 100644 --- a/tests/test_mcp_read_surface.py +++ b/tests/test_mcp_read_surface.py @@ -365,6 +365,16 @@ def test_mcp_preflight_freshness_is_unavailable_for_empty_project_scope(tmp_path assert preflight["facts"] == [] assert preflight["freshness"] == "unavailable" + # Filigree seam, scope-independent no-silent-clean (production blocker #5, paired with + # tests/contracts/test_filigree_contract.py): even on an EMPTY scope, linked-work absence + # is reported in-band as `linked_work_facts_unavailable`, never an empty-but-ok result. + warnings = cast(list[dict[str, Any]], preflight["warnings"]) + linked_work = next(w for w in warnings if w["code"] == "linked_work_facts_unavailable") + assert "Filigree" in linked_work["message"] + assert linked_work["severity"] == "info" + assert linked_work["freshness"] == "unavailable" + assert linked_work["provenance"]["inputs"] == [] + def test_mcp_preflight_soft_degrades_an_unresolvable_requirement_id(tmp_path: Path) -> None: service = service_for(tmp_path) @@ -398,6 +408,32 @@ def test_mcp_preflight_labels_corpus_fallback_requirements_nearby_not_touched(tm assert "requirement_touched" not in fact_kinds +def test_mcp_preflight_emits_orphaned_entity_link_for_a_stale_entity_trace(tmp_path: Path) -> None: + """Production blocker #4 test-hardening: orphaned_entity_link emits but had zero + behavioral coverage. A scoped requirement whose entity trace went stale/orphaned must + surface an orphaned_entity_link warn fact citing the offending trace.""" + service = service_for(tmp_path) + requirement_id = approve_requirement(service) + stale = service.create_trace_link( + TraceRef("file_ref", "src/legacy_auth.py"), + "fragile_satisfies", + TraceRef("requirement_version", f"{requirement_id}@1"), + actor="human:john", + authority="accepted", + ) + service.mark_trace_stale(stale.id, actor="agent:codex", reason="content changed") + surface = PlainweaveMcpSurface(tmp_path) + + preflight = data( + surface.plainweave_preflight_facts_get(scope_kind="explicit_requirements", requirement_ids=[requirement_id]) + ) + + orphaned = [fact for fact in preflight["facts"] if fact["kind"] == "orphaned_entity_link"] + assert len(orphaned) == 1 + assert orphaned[0]["severity"] == "warn" + assert stale.id in orphaned[0]["evidence_refs"] + + def test_mcp_entity_intent_context_returns_peer_ready_entity_facts(tmp_path: Path) -> None: service = service_for(tmp_path) seed = seed_loomweave_catalog(tmp_path)