Skip to content

Release 3.0.0 (major) — outstanding work integrated; breaking bundle in progress#52

Open
tachyon-beep wants to merge 141 commits into
mainfrom
release/3.0.0
Open

Release 3.0.0 (major) — outstanding work integrated; breaking bundle in progress#52
tachyon-beep wants to merge 141 commits into
mainfrom
release/3.0.0

Conversation

@tachyon-beep

@tachyon-beep tachyon-beep commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

3.0.0 — major release train

Opens the SemVer-major boundary to land the deferred breaking wire-surface
changes that could not ship mid-2.x without breaking federation consumers
(Clarion / Wardline / Shuttle). Draft until the breaking checklist below is
complete and a consumer-migration window is published.

Full plan + file manifest: docs/plans/2026-06-05-3.0.0-release-plan.md.

Already landed (this PR so far)

Source branch Nature Summary
codex/loom-feedback-tickets feature loom close-on-fixed cascade via scanned_paths; ADR-029 entity-association polish; doctor contracts
bolt-opt-issue-batch-query perf dropped redundant open-blockers query in batch fetch
palette-aria-labels a11y ARIA labels on icon-only dashboard buttons (dup commit collapsed)
dependabot/uv/starlette-1.0.1 deps starlette 0.52.1 → 1.0.1 (major); suite green
fix/anchor-discovery-hardening security re-implemented, not merged — see note below

CI gate (merged branch): ruff ✓ · ruff format ✓ · mypy (105 files) ✓ · pytest (full, 4 skips) ✓ · biome JS ✓.

Loomweave / Weft rebrand (landed — 7 commits, schema v26)

The full federation-contract rebrand (epic filigree-1d08ffb493) landed as the
last bundle on this branch. Hard wire-break, no compatibility aliases (owner
decision) — Filigree was the last federation member still on the old names;
Loomweave/Wardline/Legis had already cut over.

Commit Tier Flips
57dd005 T2A Clarion→Loomweave internal code identifiers
13a2331 T2B generations/loomgenerations/weft (by-identifier)
e5fd2d1 T0 v26 migrationclarion_entity_idloomweave_entity_id (column/PK/index) + rewrite every stored clarion:eid:loomweave:eid: SEI prefix (entity_associations, deleted_issues F5 tombstone array, entity-association audit events) + CLA-LMWV- rule-ids
7941702 T0 SEI_PREFIXloomweave:eid:
081675e T0 registry_backend/[clarion]loomweave + one-shot rename-on-load config shim
56e2ec6 T1 /api/loom/*/api/weft/*, protected_paths, path-scope auth, CLARION_LOOM_TOKENWEFT_TOKEN
d1bfcd1 T3 CHANGELOG [3.0.0], docs, CI lane names, contract fixtures, dashboard JS

Verification: ruff · ruff format · mypy (109 files) · biome JS · make coverage-floors all ✓. Full suite green except one pre-existing flaky observations concurrency test (test_concurrent_create_returns_existing_under_dedup_race, passes in isolation). Brand sweep clean modulo intentional migration/shim references; weftweave substring tripwire clean.

Operational (deploy-time, not code — need an owner):

  • Set WEFT_TOKEN in every deployment env (CLARION_LOOM_TOKEN is no longer read).
  • Legis re-sign pass: re-cut the HMAC for every governed binding over the renamed loomweave:eid: entity_ids (T0b filigree-2cf022fff2). Stored signatures are stale-pending-reissue by design; Filigree never verifies them, so reads do not break.

Note: "Task 7" (token audience loom→weft) is a no-op in Filigree code — the federation bearer token is opaque (hmac.compare_digest), with no audience/JWT claim. The hub mirror ~/weft (sei-standard.md/contracts-index.md) was updated to loomweave:eid: out-of-band (separate repo, uncommitted).

Intentionally NOT renamed (quarantined — hub hasn't locked them, filigree-73a2d91f5c): registry error codes CLARION_REGISTRY_VERSION_MISMATCH / CLARION_OUT_OF_SYNC, the loom:// URI scheme, the capabilities probe, the Legis surface name.

Cosmetic residuals (non-blocking follow-up): test-infra file/dir renames (test_clarion_*.py, tests/_fakes/clarion_http.py, tests/fixtures/contracts/loom/), api_loom_* handler function names + is_loom_scoped_path, bare loom prose in src docstrings, and historical ADR bodies (kept per "don't rewrite shipped ADRs").

⚠️ Deviation: the security branch was ~90% already in main

fix/anchor-discovery-hardening was 190 commits stale. Most of its hardening
(permission/decode/empty-gitdir handling) already landed in main's refactored
_read_gitdir_pointer, and it imported find_filigree_anchor — a symbol since
renamed to find_filigree_conf. Forcing the cherry-pick would have imported dead
symbols and resurrected superseded inline code. Instead, the one genuinely
missing control
— bidirectional worktree back-pointer verification (anti-spoof /
anti-stale-pointer) — was re-implemented fresh against current code, with focused
TDD tests (0fd7e3d). You're getting one security control, not a whole branch.

Breaking-work checklist (required before this leaves draft)

  • filigree-7771610917 — MCP tool namespacing (~115 tools) + dual-resolution alias window (ADR-016) — largest; ripples to all consumers
  • filigree-45d76e71bb — De-Clarionize entity-association naming (compat alias) (in_progress; partially merged)
  • filigree-e4181ae767 — Remove deprecated get_stats alias keys
  • filigree-9b4bb6e52eTransitionMode enum replacing backward bool
  • filigree-d25e75cebfsafe_message parity for claim/transition errors
  • filigree-81d3971467 — Transport-bound actor identity verification (likely the schema v24 bump)

Docs to create/update before release

  • Create ADR-029 (entity-association opacity) — cited by code but missing
  • Create ADRs for TransitionMode + transport-bound actor identity
  • Create the 3.0.0 consumer-migration guide (old→new MCP tool-name map, get_stats key migration, entity_id rename)
  • Update docs/UPGRADING.md, docs/MIGRATION.md, docs/mcp.md, docs/api-reference.md
  • Archive shipped 2.0/2.1 plans under docs/plans/completed/

Note: instruction-file untracking (out of scope, left in working tree)

The filigree instruction-manager rewrote .gitignore and untracked
AGENTS.md / CLAUDE.md this session. Deliberately kept out of these
commits — adopt or revert as a separate decision.

🤖 Generated with Claude Code

google-labs-jules Bot and others added 30 commits June 4, 2026 16:40
Co-authored-by: tachyon-beep <544926+tachyon-beep@users.noreply.github.com>
- Add missing `aria-label`s to `&times;` icon-only `<button>` tags within dynamically rendered HTML across `detail.js`, `files.js`, `graph.js`, and `workflow.js`.
- Fixes accessibility gaps where screen reader users would not be able to discern the purpose of the buttons.
- Documented findings in `.Jules/palette.md`.
- Ran `pnpm lint:fix` and `pnpm format` which applied cleanups in the JS files as well.

Co-authored-by: tachyon-beep <544926+tachyon-beep@users.noreply.github.com>
- Add missing `aria-label`s to `&times;` icon-only `<button>` tags within dynamically rendered HTML across `detail.js`, `files.js`, `graph.js`, and `workflow.js`.
- Fixes accessibility gaps where screen reader users would not be able to discern the purpose of the buttons.
- Documented findings in `.Jules/palette.md`.
- Ran `pnpm lint:fix` and `pnpm format` which applied cleanups in the JS files as well.
- Fixed a failing unit test `test_graph_api.py::TestGraphFrontendContracts::test_topology_change_reuses_positions_only_when_all_nodes_have_positions` which asserted string equality on `Object.prototype.hasOwnProperty.call`, which was auto-linted to `Object.hasOwn()`.

Co-authored-by: tachyon-beep <544926+tachyon-beep@users.noreply.github.com>
Bumps [starlette](https://github.com/Kludex/starlette) from 0.52.1 to 1.0.1.
- [Release notes](https://github.com/Kludex/starlette/releases)
- [Changelog](https://github.com/Kludex/starlette/blob/main/docs/release-notes.md)
- [Commits](Kludex/starlette@0.52.1...1.0.1)

---
updated-dependencies:
- dependency-name: starlette
  dependency-version: 1.0.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Wardline opens and reopens tracked issues from findings but never closed
them when the code was fixed and re-scanned: the absent-fingerprint sweep
only visited files present in the batch, so fixing the last/only finding in
a file (clean file, absent from `findings`) left the finding open and the
linked issue open.

Consume Wardline's `scanned_paths` (the authoritative scanned-file set,
including clean files) and drive the sweep off the union of
files-with-findings and scanned_paths, then fire a close cascade from ingest
symmetric to the existing reopen-on-regress cascade.

- scanned_paths: optional, wire-compatible request field on
  POST /api/loom/scan-results (and the classic/living aliases). With it
  non-empty, mark_unseen=true + findings=[] is now a valid fully-clean scan
  instead of a 400. Unknown clean paths are skipped (lookup, not upsert).
- Close cascade: best-effort, own BEGIN IMMEDIATE, the done-category guard
  preserves terminal human decisions, failures ride out in warnings (the
  wire) plus per-failure logs.
- Sibling-open guard: never close an issue while another linked finding is
  still an active defect (checked under the writer lock); also protects the
  clean-stale path (shared tx). Same-batch regressed issues are excluded
  from the close so reopen wins over close.

Tests (no clean-stale call): no-decoy headline (fix the only finding in a
file -> issue closes), mixed file, reopen-after-close, human-decision
preserved, idempotent-with-clean-stale, no-spurious-close, fully-clean scan,
unknown path no-op, sibling-open, same-batch collision, plus API-layer
round-trip / validation / back-compat. Contract fixture + CHANGELOG updated.

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

Bundled in-flight loom-feedback work on the cross-product entity-association
(ADR-029) surface, carried in the working tree alongside the close-on-fixed
cascade:

- db_entity_associations: document the two-axis freshness/orphan model per
  ADR-017 — Filigree owns the content axis (freshness_status); the identity
  axis is Clarion's, so the non-orphaned value is "unknown", never "active".
- db_base / db_entity_associations: declare `_skip_begin` on the
  add_entity_association protocol stub and its @_in_immediate_tx-decorated
  implementation, matching the convention of the other tx-wrapped methods
  (reopen_issue et al.) so the stub-signature contract test and the mypy
  override check agree.
- dashboard detail view: only badge a known content-axis freshness state on
  the forward (issue->entity) lookup, which never carries Clarion's current
  hash.
- doctor: document the deliberate check-id collapse in the machine summary
  (failed wins, fixed only when nothing under that id failed).
- tests: promote-finding-and-attach-entity coverage incl. retry convergence
  after an attach failure.

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

Port the security-relevant half of the abandoned anchor-discovery-hardening
branch (190 commits stale; its other hardening — permission/decode/empty-gitdir
handling — already landed in the refactored `_read_gitdir_pointer`, and its
import of `find_filigree_anchor` predates the rename to `find_filigree_conf`).

The one genuinely-missing control: `_main_worktree_from_git_path` redirected to
a main worktree on the strength of the worktree's `.git` pointer alone, without
checking git's bidirectional linkage. A spoofed `.git` file (shipped in an
untrusted clone, aimed at a victim project's `worktrees/<name>` admin dir) or a
stale pointer (admin dir renamed, worktree removed but `.git` file lingering)
would silently redirect discovery onto the wrong project's database.

Now verify the admin dir's `gitdir` back-pointer resolves back to *this* `.git`
file before redirecting; on mismatch or read failure, `.git` stands as a
boundary. This also tightens `_classify_git_entry` (its second call site): a
spoofed pointer now classifies as a plain gitdir_file, not a worktree_pointer.

Test fixtures now write the back-pointer git always creates on disk (the prior
skeleton omitted it, which is why HEAD could drop the check without failures).

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

- pyproject 2.3.0 -> 3.0.0 (major boundary for the deferred breaking wire work)
- CHANGELOG: promote merged work to [3.0.0] with a major-release preamble that
  states the breaking items are tracked in the PR and land incrementally
- docs/plans/2026-06-05-3.0.0-release-plan.md: archive/rename/update/create
  file manifest + breaking-work checklist + the instruction-file untracking note

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

- .jules/bolt.md + .Jules/palette.md rode in via the bolt/palette merges; they
  are bot learning notes, not release content, and the .jules vs .Jules case-only
  difference collides on case-insensitive filesystems.
- CHANGELOG had an empty [Unreleased] directly above [3.0.0] - Unreleased.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The loom federation hub at ~/loom is now the single authoritative source
for federation-wide patterns (roster, axiom, identity model, contract
index, URI-scheme status). Filigree retains authority over its own
surface (HTTP generations, route shapes, MCP tools, schema, contracts).

This commit adds pointers to ~/loom for the stale roster/axiom framing
while preserving all Filigree-owned route/envelope/contract definitions:

- ADR-002: roster pointer note (was "Clarion/Wardline/Shuttle/filigree";
  now 5 members per ~/loom/doctrine.md, Shuttle a thought-bubble);
  refreshed the stale loom-doctrine reference. Generation decision kept.
- docs/federation/contracts.md: authority note + roster framing point to
  ~/loom/doctrine.md and ~/loom/contracts-index.md. Endpoint/envelope
  specs (incl. classic /api/issue/... entity-association routes) unchanged.
- README.md: added a Loom-federation pointer to ~/loom; kept product copy.
- loom-uri-spec.md + planning-deprecation.md: noted loom:// federation
  status now lives at ~/loom/uri-scheme.md (closed, superseded by SEI;
  shuttle:// unresolved as Shuttle has no repo). Specs preserved.
- shuttle-design.md: roster/status pointer (extra doc found restating the
  federation member set).

ADR-029 internals untouched (Filigree-owned; loom points to it).

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

INVALID_TRANSITION errors told callers to "Use get_valid_transitions() to
see allowed transitions" — a deferral that named the wrong surface for a CLI
caller and the *old* MCP tool name (renamed to workflow_transition_list). The
non-JSON CLI path prints only the message, so this was the sole information a
terminal user saw. And `close` swallowed the computed valid_transitions in its
generic ValueError arm, emitting a bare envelope unlike update/release/start.

Two fused defects, fixed at the leverage point — lower layers state facts
(reachable target states, no tool name); each surface adds its own affordance:

- db_issues/templates: inline the allowed states into the rejection message
  ("Allowed from 'fixing': verifying."), degrading cleanly for terminal states.
  Drops the stale get_valid_transitions() reference. Shared formatter added as
  types.api.allowed_transitions_clause.
- close CLI: route InvalidTransitionError through _transition_error_payload so
  the structured valid_transitions/hint flow through both single-id and batch
  envelopes, matching its sibling commands.

Audit ("look across all commands") — also inline the other genuine deferrals
where the answer was locally computable:
- unknown-pack (MCP + CLI guide): list real pack/type names.
- scanner-not-found (MCP + CLI list): list the bundled scanner names.

Left as-is (legitimate, not laziness): tool docstrings cross-referencing
sibling tools; doctor/init escape hatches in fatal states.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CLAUDE.md was removed from the repo in 55e709d ("Removed agents files") but
test_live_docs_do_not_reference_old_mcp_tool_names still listed it in
LIVE_AGENT_DOCS, so the docs-contract suite went red with FileNotFoundError.
The live agent-facing docs now live in instructions.md + the filigree-workflow
skill (still covered). Drop the dead entry and fix the comment's file count
(9 docs - CLAUDE.md = 8; 2 observe-exempt -> "other 6").

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

Schema v24 (verified_actor on event-bearing tables) + CLI OS-user + MCP-stdio
parent attribution; session-level identity on FiligreeDB; record-both-and-warn
on mismatch. Keystone schema change the other 3.0.0 breaking tickets build on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds verified_actor/verified_author to events, file_events,
annotation_events, comments, observations. Nullable, no backfill;
events dedup index unchanged. ADR-012.

Output shape tracks schema mechanically (SELECT * -> dict(row)), so the
observation read path surfaces verified_actor and the create-return
literal stamps it None to keep create==list shape; ObservationDict and
the CLI shape contract move with it. Value-stamping is a later task.

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ADR-012 Task 5 follow-up)

CommentRecord gained a required verified_author field in b9de64e but the
dashboard POST-comment route's response literal was not updated, breaking
the full-package mypy gate. Read the stored value back so the response is
truthful regardless of whether this route stamped the column.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
comment_to_mcp spreads the full CommentRecord, so the v24 verified_author
column now rides along on the MCP comment result. Update the stale contract
assertion. Loom federation surface is unaffected (explicit adapter).

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tachyon-beep and others added 25 commits June 9, 2026 09:38
_merge_into_survivor collapsed a duplicate (issue_id, locator) binding into
the (issue_id, sei) survivor but carried only content_hash_at_attach +
attached_at — never the Legis sign-off columns. Deleting a signed non-survivor
row dropped its signature, silently downgrading the issue governed->ungoverned
(DECISION 1A), after which the closure gate short-circuits to PROCEED with no
Legis call.

Carry the freshest valid sign-off onto the survivor (signed wins, never
downgrade, higher signoff_seq when both signed), mirroring the v27 upsert
stickiness in db_entity_associations. Even in the content-mismatch edge the
result is governed-but-STALE -> fail-closed, so no path silently PROCEEDs.

Guard tests: signed non-survivor carries forward; signed survivor not
downgraded by an unsigned incoming; freshest signoff_seq wins both directions.

filigree-c31a22fd47

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "3.0.0 tool names" admonition backticked the removed flat aliases
(get_issue, list_findings, start_work) to explain the rename, which tripped the
doc guards (test_no_markdown_doc_names_an_old_tool and
test_live_docs_do_not_reference_old_mcp_tool_names) and reddened the suite. Drop
the inline old-name list — the new names stay, and the full old->new table is
the migration guide's job (MIGRATION-3.0.md, which the guards do not scan).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The doc-example linter joined backslash continuations but not open-quote
continuations, so a `filigree` example whose quoted argument spans newlines
(valid shell, e.g. a multi-line -d "..." body) was captured as an
unbalanced-quote fragment and skipped — its verb/options escaping drift-checking.
Three real examples were silently unguarded.

Extract the per-doc parsing into _command_lines_in_text and keep consuming raw
lines until the quotes balance (or the block ends, preserving the skip safety
valve for genuinely malformed examples). The three previously-skipped examples
now assemble, validate, and pass (67 passed, 0 unparseable skips).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The module-level comment still flatly claimed the server-mode .mcp.json
"is written 0600", contradicting the runtime fix (0134f59) that stats and
reports the real mode because chmod is a best-effort no-op-returning-success on
filesystems like WSL DrvFs / CIFS. Reword to "chmod-tightened toward 0600
(best-effort)" so the prose matches the behaviour — the last residue of prong 3.

filigree-ebfc16a090

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

Two silent best-effort paths in the store/install surface now log when they
fail for a real reason, instead of hiding a hygiene gap:

- migrate_store_to_weft: the legacy-husk federation_token unlink was wrapped in
  contextlib.suppress(OSError). missing_ok=True already absorbs the absent-file
  case, so any OSError reaching the suppressor is a real failure (e.g.
  PermissionError) that leaves a now-dead per-machine secret behind with zero
  signal. Warn so the operator can clean it up.
- _git_tracks: an inconclusive ls-files probe (raised exception, or a non-{0,1}
  return code such as 128 "not a repo" / index-lock contention) was
  indistinguishable from a clean "not tracked" (rc 1) answer, silently silencing
  the "already-tracked -> git rm --cached" token warning the caller derives from
  it. Log both inconclusive cases at debug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The editable filigree pin in the lockfile still read 3.0.0rc9 after the version
bump landed in 7bcc6c3; resync it to rc10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
READ_ONLY_CODEBASE_AUDIT_2026-06-04*.md were git-tracked and shipped in
the 3.0.0 sdist (~79KB of internal agent audit notes reaching published
consumers; the wheel was already clean). git rm --cached both and add a
.gitignore pattern so they stay local-only. Files remain on disk.

Closes filigree-107e275ab3 (PR #52 epic, B6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both file_events import INSERTs (merge + non-merge) listed verified_actor
but omitted actor, so an export->import round-trip silently reset the
claimed actor to '' even though write paths (register_file(actor=...))
stamp a real value and export dumps it. Add actor to both INSERTs; the
merge dedup identity (WHERE NOT EXISTS) is unchanged. Audit-trail
fidelity fix, not a security hole — actor is the self-claimed identity;
the trustworthy verified_actor already round-tripped.

Tests: two new round-trip assertions (non-merge + merge) in
test_verified_actor.py, verified red-then-green.

Closes filigree-673a1f3af5 (PR #52 epic, B4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bearer-auth middleware (dashboard_auth) is built on Starlette 1.0
BaseHTTPMiddleware/Request semantics, but the floor floated via fastapi,
whose requires-dist is starlette>=0.46.0 (unbounded). uv.lock pinned
1.0.1 for in-repo CI, but published-wheel consumers resolve from
pyproject and a future re-lock could cross the 0.x->2.x boundary and
break auth. Declare the constraint directly; lock unchanged (1.0.1).

Closes filigree-d57e5a5b66 (PR #52 epic, W2).

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

These three modules carry the Legis closure-gate, transport-bound actor
identity, and governance-client logic but had no per-module floor, so a
single-module coverage collapse could hide under the aggregate 85% gate.
Add explicit FILE_FLOORS a few points below measured (actor_identity 95,
governance 90, legis_client 85).

Closes filigree-d8156e627b (PR #52 epic, T4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
check_closure_gate attached the LEGIS_API_TOKEN bearer to a plain urllib
request whose default redirect handler copies request headers onto the
redirect target with no same-origin check, so a 302 from a compromised
or open-redirecting Legis could re-send the token to an attacker host.
Route the request through a custom opener whose redirect handler drops
Authorization before following a redirect (benign redirects still
follow; normal non-redirect token auth unchanged), and refuse a
non-http(s) LEGIS_URL before attaching the bearer or making any request.

Defense-in-depth: exploitation needs a Legis-side open-redirect or a
compromised Legis, not a critical on its own.

Tests (TDD, red-then-green): two-server redirect->sink harness in
tests/_fakes/legis_http.py; test_redirect_does_not_leak_bearer_token,
test_non_redirecting_gate_still_sends_token, test_non_http_scheme_refuses
in test_legis_client.py. ruff + mypy clean; CHANGELOG Security entry.

Closes filigree-93c1d49f2a (PR #52 epic, B3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_id (FIL-2/X-5)

finding_list's filters were flat top-level columns — no way to exclude
wardline's kind:metric engine telemetry (158 of 160 findings in the live
store) or baselined/suppressed defects. To answer "show me the real
un-suppressed defects" an agent had to pull every finding and filter nested
metadata.wardline.* client-side. Wardline's producer-side `scan where:{}`
grammar already filtered with more power than its consumer.

Port the relevant axes onto finding_list as flat params (consistent with
filigree's API style; functional parity, not JSON-shape parity):
- kind        — metadata.wardline.kind enum (defect/fact/classification/metric/suggestion)
- suppression — active|baselined|waived|judged over metadata.wardline.suppression_state;
                active = the un-suppressed population
- qualname    — metadata.wardline.qualname (exact match)
- rule_id     — the conspicuously-missing top-level column filter

Headline query: `finding_list kind=defect suppression=active`.

Wired through the single chokepoint (db_files.list_findings_global) so every
surface gets parity: MCP finding_list tool + handler + ListFindingsArgs, CLI
list-findings (--kind/--suppression/--rule-id/--qualname), HTTP
GET /api/weft/findings, and the /files/_schema discovery endpoint. sink/tier
skipped (absent at the filigree<-wardline seam); path_glob deferred (weft-2b71565563).

The suppression=active predicate is the SAME shared SQL helper
(_wardline_suppressed_sql) as unbridged_finding_stats' "actionable" split, so
the active/suppressed classification cannot drift — though the populations
differ (unbridged_finding_stats also restricts to open+unbridged, so its count
is a subset). json_valid guards corrupt-metadata rows from OperationalError;
absent/corrupt verdict reads as active.

Also fixes a pre-existing connection leak surfaced by the new tests' GC
pressure: the mcp_surface fixture in test_cross_surface_parity.py was a
`return db`-style fixture that never closed its sqlite connection, tripping the
suite's -W error ResourceWarning gate non-deterministically. Converted to
yield + close (mirrors the sibling dashboard_surface fixture).

Tests: core/MCP/CLI/HTTP filter coverage incl. corrupt-metadata, the
shared-predicate classification vs population-superset distinction, and qualname
at every surface. ruff/format/mypy clean; full suite green under
-W error::ResourceWarning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review nit: test_redirect_does_not_leak_bearer_token asserted only that
the SINK saw no Authorization, so in isolation it couldn't distinguish
"bearer stripped at the redirect" from "bearer never sent". The redirect
(origin) server now records the Authorization it received, and the test
asserts the bearer DID reach the origin (hop 1) but did NOT cross to the
sink — proving the strip happens precisely at the redirect boundary
without relying on the companion non-redirect test.

Test-only; no production change. Follows up filigree-93c1d49f2a (B3).

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

Residual of the wardline->filigree N2 seam (weft-171fc22a50). A plain finding
work-query (`finding_list` / `list-findings`) returned wardline-baselined/
waived/judged findings mixed with real ones at status:open severity:high --
annotated via suppression_state but not hidden -- so `finding_list status=open
severity=high` still surfaced already-accepted defects as fresh, open work.

Both agent surfaces now default to suppression='active'; pass suppression='all'
to include suppressed rows, or a specific verdict to triage them. The
default-hide lives at the agent surfaces ONLY:

- core list_findings_global keeps its all-inclusive default (suppression=None);
  internal callers (e.g. scanner_reporting re-finding a just-ingested row) are
  unaffected.
- 'all' is a new accepted suppression value = explicit no-filter sentinel (no-op).
- the federation read API (GET /api/weft/findings) and the dashboard (per-file
  /files/{id}/findings) are deliberately UNCHANGED -- machine/human read
  surfaces whose consumers pass an explicit suppression= filter; guarded by a
  regression test so a sibling's machine-read contract is never silently
  narrowed.

Complements the already-shipped finding_promote suppression guard and the
session-context actionable/suppressed split. Seam verified against wardline's
to_filigree_metadata: metadata.wardline.suppression_state in
{baselined,waived,judged}, absent => active.

Tests: surface default-hide + 'all' opt-in + literal ticket repro
(status=open severity=high) on MCP/CLI; core 'all' no-op + default-stays-all
contract lock + off-contract non-dict metadata.wardline + pagination-after-
filter; API capability advertises 'all' + federation-default-stays-inclusive
guard. Docs: CLI/MCP reference + CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tract F7 (filigree-2bdb878bd2)

Names the deliberate read-surface asymmetry so it is a contract, not an
implicit behaviour: agent surfaces (MCP finding_list / CLI list-findings)
default suppression=active; the machine/federation surface
(GET /api/weft/findings) defaults to all (inclusive); the dashboard
(per-file) is unchanged. Documents that suppression_state is a first-class
top-level field on ScanFindingWeft — consumers filter via ?suppression= or the
top-level field and must never reach into the member-specific nested
metadata.wardline blob. Records the reversal trigger: the machine surface stays
inclusive-by-default until a real consumer wants default-hide there.

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

An unscoped classic-router write (POST /api/issues) in server mode silently
resolved to the daemon's default project: ProjectMiddleware set no project
ContextVar, _get_db fell back to default_key, and the X-Filigree-Project echo
was gated behind is_loom_scoped_path — so the misroute was undetectable.

Broaden the echo to every non-/mcp response (scoped branch echoes the resolved
key, unscoped branch echoes default_key), uniform with the federation read
seam. The federation-write fail-closed stays is_federation-gated and untouched
— classic writes are NOT failed closed because the dashboard's no-project
default view legitimately writes to /api and relies on default resolution.

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

server.json entries are keyed by store-dir string, but a project's identity is
its root. A .filigree -> .weft/filigree store relocation re-registered under
the new store dir while the old key lingered with the same prefix, so the
collision check read it as a conflict and raised ValueError, refusing the move.

Dedup by project root via the existing anchor-based _project_root_from_store_dir
helper: any existing entry resolving to the same root is the same project's
stale key, so drop it instead of colliding. Converges to a single live key and
self-heals. A running daemon's in-memory project_store is out of scope — this
fixes the durable server.json record; the daemon reconciles on next reload.

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

Step 2's metadata sub-tree copy (scanners/, templates/) used shutil.copytree
straight to the final path, guarded on `not dest.exists()` — the same
torn-then-skip pattern already fixed for the DB copy and the metadata files: a
crash mid-copytree leaves a PARTIAL directory at the destination that a re-run's
existence guard mistakes for a finished copy and skips, publishing the partial.

Add _atomic_copy_tree (copytree into a dest-dir temp, then os.replace) so dest
only ever appears complete; a crash leaves only the temp (cleaned up), and the
copy-once guard stays but is now safe because a present dest is always finished.

Regression test simulates a crash mid-copytree and asserts no partial is
published and a re-run re-copies the complete tree.

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

An ad-hoc writer (a fresh CLI/MCP process) that opened the legacy DB AFTER the
daemon detect-and-refuse check passed and committed during the copy→unlink
window had that commit orphaned on the about-to-be-unlinked inode. The daemon
refuse handles long-lived idle connections; this closes the complementary
active-writer case.

Hold BEGIN IMMEDIATE on the legacy DB across the whole snapshot→conf-rewrite→
unlink window (_migration_write_fence): BEGIN IMMEDIATE excludes all other
writers, so no foreign commit can land between the snapshot and the unlink, and
a writer already holding the lock makes acquisition raise StoreMigrationBusyError
before any mutation (superseding + widening the old copy-time checkpoint-busy
guard). The DB copy switches from checkpoint+file-copy to the SQLite online-
backup API (_snapshot_copy_sqlite_locked), which folds WAL-resident commits and
preserves the FILG application_id without a checkpoint — checkpointing cannot run
under the fence (it self-conflicts / returns busy). Backup runs from a fresh
reader, never the fence holder (that hangs). Empirically validated: backup-under-
sibling-fence completes + preserves data/app_id, the fence blocks a 3rd writer,
and unlink-under-fence + rollback/close is clean (POSIX open-fd semantics).

Removes the now-unused _checkpoint_and_copy_sqlite; updates the WAL-fidelity
test rationale and the crash test to target the new backup/atomic-publish seam;
adds TestMigrationWriteFence (fence blocks concurrent writer, refuses on held
writer, locked snapshot preserves WAL+app_id).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pth, not a full close (filigree-39c6958f31)

Systematic-debugging follow-up: a discriminating thread test showed the fence
RELOCATES the ad-hoc-writer data-loss rather than eliminating it. A writer that
opens the legacy DB and blocks on the fence during the sub-second hold, with a
busy_timeout longer than the hold (FiligreeDB sets 5s), commits to the orphaned
inode AFTER the fence releases and the legacy files are unlinked — silently lost
(POSIX keeps an open fd writing to a deleted inode; the migration cannot stop a
writer it does not cooperate with).

No behaviour change — the fence is still worth shipping as defense-in-depth: it
refuses migration when a writer is ALREADY active at acquire (StoreMigrationBusyError,
superseding the copy-time checkpoint-busy guard) and gives short-/zero-timeout
writers a visible SQLITE_BUSY. But the prior commit's docstrings/comments
overclaimed 'no foreign commit can land' as a closure. Correct them to state the
known residual plainly and credit the MANDATORY operator quiesce (docs/UPGRADING.md)
+ daemon detect-and-refuse as the real backstops — the ticket's own option 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… fixes (filigree-197be8b501, filigree-39c6958f31)

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

Adopt the Weft Federation "Loom" design system as a full re-skin of the
Filigree docs site, and add a narrative page documenting Filigree's role
in the federation and how it engages each member.

- Re-vendor the canonical shared docs theme (loom-mkdocs.css ->
  weft-mkdocs.css): warm espresso ground + dyed-amber accent, per-member
  thread palette. Filigree keeps its sky thread (#56B7E2) as its member
  accent. extra.css repainted onto the warm ramp.
- Rebuild overrides/home.html: hero corrected to 116 MCP tools (was 114),
  the old "Loom Federation suite" grid replaced with a Weft Federation
  roster (Loomweave/Filigree/Wardline/Legis/Charter, Filigree "you are
  here", Lacuna adjacent-note). Sibling links point at repos (only
  wardline.foundryside.dev resolves; clarion/legis/charter subdomains 404).
- New docs/federation/index.md "Filigree in the Weft Federation": role as
  work-state surface, the two structural facts (SEI LOCKED 2026-06-05,
  stored opaque; weft generation as additive-only transport per ADR-002),
  per-member bindings each with its caveat carried verbatim (Wardline->
  Filigree A-1 LIVE), and the anti-claims (no weft:// scheme, no broker).
- Fix invisible primary-CTA label: Material's `.md-typeset a` color
  out-specified `.fg-btn--primary`, painting it sky-on-sky. Raise the
  button color selectors' specificity; verified in dark + light.
- copyright.html footer: "Loom suite" -> "Weft Federation"; Clarion ->
  Loomweave.

mkdocs build --strict green. Docs-only; no source changes.

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

The legacy .filigree/ store was the stale-key source behind the federation
emit drift (weft-7436c1959e, C2 root cause). With the canonical
.weft/filigree store registered on the shared :8749 daemon and emit verified
(findings land in .weft/filigree, not here), the legacy dir is retired.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(filigree-c3e2b72f21)

Every FindingsSummary producer (get_file_findings_summary,
get_files_findings_summary, get_global_findings_stats, and the inline
list_files_paginated / loom file-list summary) bucketed severity by open
status alone, so a baselined HIGH was indistinguishable from an active HIGH and
federation consumers over-reported actionable high/critical work by the count
of already-accepted findings.

Additive fix: the existing top-level buckets keep their meaning (every open
finding, suppression-agnostic) and a parallel `suppressed: SeverityBreakdown`
is added, computed with the same shared classifier as the row-level
finding_list suppression filter so the two cannot drift. Consumers derive
actionable work as `bucket - suppressed[bucket]`; no existing number changes.
GlobalFindingsStats and EnrichedFileItem.summary inherit the key. Counterpart
of weft federation interface audit gap G4.

(CHANGELOG entry for this change lands with the following commit, which shares
the file.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fig.json (filigree-4bf16e64b6)

Completes the WEFT store consolidation for config the way migrate_store_to_weft
did for the database. The project anchor moves off the legacy root file
.filigree.conf into the store's own config.json (the sole-writer subtree).

- filigree init imports a present conf into .weft/filigree/config.json (conf-wins
  on prefix/enabled_packs/registry_backend/loomweave/project_name; mode stays
  config.json-authoritative; db dropped) and retires it to .filigree.conf.imported
  — idempotent, crash-convergent (config.json written atomically before the conf
  is atomically renamed). Fresh installs are born confless.
- New anchor = presence of .weft/filigree/ (or a weft.toml [filigree].store_dir
  override, now discoverable via a new resolve_store_dir-based anchor rung).
  weft.toml is never written by filigree and never holds identity (C-9c deletion
  test: filigree boots with no weft.toml).
- find_filigree_anchor gains include_legacy_dir=True; the three implicit-startup
  surfaces (generate_session_context, the agent dashboard hook, stdio-MCP with no
  --project) pass False so a bare legacy .filigree/ ancestor is still not treated
  as attach-consent. The .git-boundary ForeignDatabaseError guard is preserved.
- Legacy .filigree/ STORE reads stay (resolve_store_dir back-compat, C-9f); only
  the conf ANCHOR is demoted to one-shot-import-and-retire. Existing installs
  migrate on their next init; nothing breaks until then.
- config.json is now the sole identity authority, so from_store_dir is STRICT on a
  present-but-corrupt config.json (raises VALIDATION at open, symmetric with
  from_conf) instead of silently defaulting to the dir name — which would fragment
  issue IDs across two prefixes. A missing config.json stays lenient.

Also authors filigree's half of the shared weft.toml schema (docs/federation/),
to be merged with loomweave-009 at the weft hub, plus a drop-in sibling-adoption
prompt. CHANGELOG carries both this entry and the G4 entry from the prior commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tachyon-beep tachyon-beep marked this pull request as ready for review June 10, 2026 00:00
Copilot AI review requested due to automatic review settings June 10, 2026 00:00
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

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